├── .babelrc.js
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .lintstagedrc.js
├── .prettierignore
├── .prettierrc.json
├── .stylelintrc.js
├── LICENSE
├── README.md
├── deploy.sh
├── docs
├── .vuepress
│ ├── config.js
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── logo.png
│ └── styles
│ │ └── palette.styl
├── README.md
├── final
│ ├── last.md
│ └── project.md
├── practice
│ ├── eslint_plugins.md
│ ├── eslint_prettier.md
│ ├── eslint_typescript.md
│ ├── hello_eslint.md
│ ├── husky_lint-staged.md
│ ├── lint-staged_tsc.md
│ └── stylelint.md
└── theory
│ └── history.md
├── package-lock.json
├── package.json
├── src
├── ButtonVue.vue
├── button.js
├── happy.jsx
├── messyCSS.css
├── messyJS.js
├── messyLESS.less
├── messySubJS.js
├── messyTS.ts
├── messyTypes.ts
├── messyTypesInfo.d.ts
└── tsx.tsx
└── tsconfig.json
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "presets": [
3 | "@babel/preset-react"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # don't ever lint node_modules
2 | node_modules
3 | # don't lint build output (make sure it's set to your correct build folder name)
4 | dist
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | // 支持浏览器环境
5 | browser: true,
6 | // 识别 CommonJS
7 | node: true,
8 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
9 | es2021: true,
10 | },
11 | extends: [
12 | "eslint:recommended", // eslint 自己的推荐规则,最佳实践最小集
13 | "plugin:prettier/recommended", // 禁用 eslint 关于代码的风格的规则,使用 prettier 的风格
14 | ],
15 | overrides: [
16 | // 处理 JS 文件
17 | {
18 | files: ["**/*.{js,jsx}"], // 只处理 js 和 jsx 文件
19 | parser: "@babel/eslint-parser", // 使用 babel 来解析 js 文件
20 | extends: ["plugin:react/recommended"],
21 | parserOptions: {
22 | sourceType: "module", // 支持 import/export
23 | allowImportExportEverywhere: false,
24 | ecmaFeatures: {
25 | globalReturn: false,
26 | },
27 | },
28 | },
29 | // 处理 TS 文件
30 | {
31 | files: ["**/*.{ts,tsx}"], // 只处理 ts 和 js 文件
32 | parser: "@typescript-eslint/parser", // 能看懂 TypeScript
33 | parserOptions: {
34 | project: ["./tsconfig.json"], // 告诉 eslint:tsconfig 在哪
35 | },
36 | extends: [
37 | // typescript-eslint 的推荐规则,只是这些最佳规则都是针对 TS 的
38 | "plugin:@typescript-eslint/recommended",
39 | // tsconfig.json 里 Type Checking 的推荐规则
40 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
41 | ],
42 | plugins: [
43 | // 使用 typescript x eslint 的插件
44 | "@typescript-eslint",
45 | ],
46 | },
47 | // 处理 vue 文件
48 | {
49 | files: ["**/*.vue"], // 只处理 vue 文件
50 | extends: ["plugin:vue/vue3-recommended"], // 使用 vue3 的推荐规则
51 | },
52 | ],
53 | };
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node template
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variables file
76 | .env
77 | .env.test
78 |
79 | # parcel-bundler cache (https://parceljs.org/)
80 | .cache
81 | .parcel-cache
82 |
83 | # Next.js build output
84 | .next
85 | out
86 |
87 | # Nuxt.js build / generate output
88 | .nuxt
89 | dist
90 |
91 | # Gatsby files
92 | .cache/
93 | # Comment in the public line in if your project uses Gatsby and not Next.js
94 | # https://nextjs.org/blog/next-9-1#public-directory-support
95 | # public
96 |
97 | # vuepress build output
98 | .vuepress/dist
99 |
100 | # Serverless directories
101 | .serverless/
102 |
103 | # FuseBox cache
104 | .fusebox/
105 |
106 | # DynamoDB Local files
107 | .dynamodb/
108 |
109 | # TernJS port file
110 | .tern-port
111 |
112 | # Stores VSCode versions used for testing VSCode extensions
113 | .vscode-test
114 |
115 | # yarn v2
116 | .yarn/cache
117 | .yarn/unplugged
118 | .yarn/build-state.yml
119 | .yarn/install-state.gz
120 | .pnp.*
121 |
122 | ### macOS template
123 | # General
124 | .DS_Store
125 | .AppleDouble
126 | .LSOverride
127 |
128 | # Icon must end with two \r
129 | Icon
130 |
131 | # Thumbnails
132 | ._*
133 |
134 | # Files that might appear in the root of a volume
135 | .DocumentRevisions-V100
136 | .fseventsd
137 | .Spotlight-V100
138 | .TemporaryItems
139 | .Trashes
140 | .VolumeIcon.icns
141 | .com.apple.timemachine.donotpresent
142 |
143 | # Directories potentially created on remote AFP share
144 | .AppleDB
145 | .AppleDesktop
146 | Network Trash Folder
147 | Temporary Items
148 | .apdisk
149 |
150 | .idea
151 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "**/*.{ts,tsx}": [
3 | () => "tsc -p tsconfig.json --noEmit",
4 | "eslint --cache --fix",
5 | ],
6 | "**/*.{js,jsx}": [
7 | "eslint --cache --fix",
8 | ],
9 | "**/*.vue": [
10 | "eslint --cache --fix",
11 | ],
12 | "**/*.{css,less}": ["stylelint --cache --fix"],
13 | };
14 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "plugins": ["stylelint-prettier"],
3 | "extends": [
4 | "stylelint-config-standard",
5 | "stylelint-config-recommended-less",
6 | "stylelint-config-prettier"
7 | ],
8 | "customSyntax": "postcss-less",
9 | "rules": {
10 | "prettier/prettier": true
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Monster
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Linter 上手完全指南
2 |
3 | > 本次实战的所有代码、配置已放在 [Github](https://github.com/haixiangyan/linter-toturial "项目 Github") 可尽情把玩。
4 |
5 | 哈喽大家好,我是海怪。
6 |
7 | 不知道大家有没有经常遇到这样一种情况:**每次新建项目项目做代码风格的配置时总是随便找一篇文章,也不管啥意思,把 `.eslintrc.js` 的配置一抄,再把对应的 NPM 包装上就算完事了。**
8 |
9 | 诶?不想承认?那考考你:`eslint`, `prettier`, `eslint-config-prettier`, `eslint-plugin-prettier`, `prettier-eslint` 这些都是个啥关系?它们的职责是什么?
10 |
11 | 再考考你:如果想用 ESLint 和 TypeScript 结合,要用到哪些包呢?`@typescript-eslint/parser`, `@typescript-eslint/eslint-plugin`, `tslint-plugin-prettier`, `tslint-config-prettier`, `prettier-tslint` 是不是有种“玩排列组合”的感觉?傻傻分不清?
12 |
13 | 
14 |
15 | ## 目的
16 |
17 | 在没深入了解这些工具之前,我也很蒙逼。网上的资源也是东一块西一块的,要不只讲 Prettier,要不只讲 ESLint,
18 | 要不都不讲直接丢一个配置。
19 |
20 | 其实把这些工具单独拆开来看,它们都是很简单的工具。但是由于前端更新速度太快了,导致出现了很多相似的轮子,比如 `tslint` 和 `eslint`,
21 | 而且这些 Linter 一旦和 ES5, ES6 新旧语法、Babel 转译、JSX 等新特性结合,事情就不再简单了。
22 |
23 |
24 | **所以,我决定出一份收敛的教程来说清楚这些工具之间的关系,以及给出日常开发的常用配置。**
25 |
26 | 你可以跟着教程自己手动配置一次,也可以只是瞧瞧看看。不管怎样,我还是希望大家不仅能知其然,也能知其所然,不要抄个配置文件就走了。
27 | 如果你读完本教程再回头来看自己手头/公司项目的 `.eslintrc.js` 配置时,能够做到一点都不慌,并且心中有数,那么这个教程的目的就达到了。**
28 |
29 | ## 内容
30 |
31 | 这篇文章主要分为两部分:
32 |
33 | * **理论篇**: 说清楚这些工具的历史和关系,让读者俯瞰整个工具生态
34 | * **实战篇**:从 0 开始配置 Linter,边实战边讲解原理,覆盖范围:ESLint x TypeScript x JavaScript x Babel x JSX x Plugin x Husky x LintStaged x StyleLint
35 |
36 | ## 花絮
37 |
38 | 这篇教程 + 项目是我花了一周的时候弄出来的。
39 |
40 | 实际上我以为只出一篇文章就能讲清楚了,
41 | 后来发现前端 Linter 衍生出来的工具实在是太多了,不弄个项目出个实战教程真的无法验证某些点。然后就出了实战篇。好吧,那就出两篇文章。
42 |
43 | 可是后来随着配置的东西越来越多,踩的坑也变得越来越多,文章的字数快超过 6000 字了,对读者来说不是一个很好的阅读体验,因此拆分了章节,
44 | 最后有了这份教程。
45 |
46 | ## 参考
47 |
48 | 下面列举我写这篇教程所看的一些参考资料,如果大家有兴趣也可以 **按下面给的顺序** 进行阅读:
49 |
50 | * [Prettier看这一篇就行了](https://zhuanlan.zhihu.com/p/81764012) 陈龙大佬的 Prettier 文章,写的非常清楚
51 | * [Prettier 文档](https://prettier.io/docs/en/index.html) 接下来再看 Prettier 文档,结合陈龙大佬的文章会有更高纬度的视角
52 | * [ESLint 文档](https://eslint.org/) ESLint 文档非常简陋,像说明书一样,很无聊,但是也要懂一点要怎么配置
53 | * [TypeScript ESLint 文档](https://typescript-eslint.io/) 这个文档远比 ESLint 文档要好,里面还说了一些实践思路,教会你 Config 和 Plugin 都是做啥用的
54 | * [StyleLint 文档](https://stylelint.io/) 经过上面文档的洗礼再看 StyleLint 会觉得很简单
55 | * [Husky Github](https://github.com/typicode/husky) 知道 Husky 是怎么在 Git Hooks 执行 Bash 的
56 | * [lint-staged Github](https://github.com/okonet/lint-staged) 知道 Husky x lint-staged 用法
57 |
58 | 下面再列举一些我搜了很多次的问题:
59 |
60 | ### LintStaged x TypeScript
61 |
62 | * [lint-staged ignores tsconfig.json when it called through husky hooks](https://github.com/okonet/lint-staged/issues/825)
63 | * [Build typescript on commit](https://github.com/okonet/lint-staged/issues/468)
64 | * [How to lint for Typescript compilation issues?](https://stackoverflow.com/questions/51428789/how-to-lint-for-typescript-compilation-issues)
65 | * [Current version incorrectly analyzes @types/node](https://github.com/gustavopch/tsc-files/issues/20)
66 |
67 | ### ESLint
68 |
69 | * ["parserOptions.project" has been set for @typescript-eslint/parser](https://stackoverflow.com/questions/58510287/parseroptions-project-has-been-set-for-typescript-eslint-parser)
70 | * [eslint-plugin-prettier is very slow](https://github.com/prettier/eslint-plugin-prettier/issues/304)
71 | * [Very slow performance](https://github.com/prettier/eslint-plugin-prettier/issues/445)
72 | * [ESLint not reporting TypeScript compiler type checking errors](https://stackoverflow.com/questions/60514929/eslint-not-reporting-typescript-compiler-type-checking-errors)
73 | * [No actual type checking?](https://github.com/typescript-eslint/typescript-eslint/issues/1037)
74 |
75 | ## 支持
76 |
77 | **原创不易,如果觉得本教程对你有帮助,希望大家多多 Star 和 Fork。
78 | 同时我也是个新人公众号号主,如果喜欢我写的内容,也欢迎关注公众号【写代码的海怪】。**
79 |
80 |
81 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # abort on errors
4 | set -e
5 |
6 | # build
7 | npm run docs:build
8 |
9 | # navigate into the build output directory
10 | cd docs/.vuepress/dist
11 |
12 | # if you are deploying to a custom domain
13 | # echo 'www.example.com' > CNAME
14 |
15 | git init
16 | git add -A
17 | git commit -m 'deploy'
18 |
19 | # if you are deploying to https://.github.io
20 | # git push -f git@github.com:/.github.io.git master
21 |
22 | # if you are deploying to https://.github.io/
23 | git push -f git@github.com:haixiangyan/linter-tutorial.git master:gh-pages
24 |
25 | cd -
26 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | base: "/linter-tutorial/",
3 | title: "Linter上手完全指南",
4 | description:
5 | "你真了解 Liner 么?本教程将从历史演进以及上手实战来聊聊 Linter 的世界。",
6 | head: [
7 | [
8 | "meta",
9 | {
10 | name: "keywords",
11 | content:
12 | "eslint, prettier, linter, lint-staged, 前端, 代码风格, 上手指南",
13 | },
14 | ],
15 | ["meta", { name: "author", content: "海怪" }],
16 | ],
17 | plugins: [
18 | "@vuepress/medium-zoom",
19 | "@vuepress/back-to-top",
20 | "@vuepress/active-header-links",
21 | ],
22 | themeConfig: {
23 | logo: "/images/logo.png",
24 | repo: "https://github.com/haixiangyan/linter-tutorial",
25 | lastUpdated: true,
26 | nav: [
27 | {
28 | text: "Issue",
29 | link: "https://github.com/haixiangyan/linter-tutorial/issues",
30 | },
31 | ],
32 | sidebar: [
33 | {
34 | title: "介绍",
35 | collapsable: false,
36 | children: ["/"],
37 | },
38 | {
39 | title: "理论",
40 | collapsable: false,
41 | children: ["/theory/history"],
42 | },
43 | {
44 | title: "实战",
45 | collapsable: false,
46 | children: [
47 | "/practice/hello_eslint",
48 | "/practice/eslint_prettier",
49 | "/practice/eslint_typescript",
50 | "/practice/eslint_plugins",
51 | "/practice/stylelint",
52 | "/practice/husky_lint-staged",
53 | "/practice/lint-staged_tsc",
54 | ],
55 | },
56 | {
57 | title: "最后",
58 | collapsable: false,
59 | children: ["/final/project", "/final/last"],
60 | },
61 | ],
62 | },
63 | };
64 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haixiangyan/linter-tutorial/6c3fe571ae970ccd7326bc1cf313da11c07ac47f/docs/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haixiangyan/linter-tutorial/6c3fe571ae970ccd7326bc1cf313da11c07ac47f/docs/.vuepress/public/images/logo.png
--------------------------------------------------------------------------------
/docs/.vuepress/styles/palette.styl:
--------------------------------------------------------------------------------
1 |
2 |
3 | // 颜色
4 | $accentColor = #4B32C3
5 | $textColor = #2c3e50
6 | $borderColor = #eaecef
7 | $codeBgColor = #282c34
8 | $arrowBgColor = #ccc
9 | $badgeTipColor = #4B32C3
10 | $badgeWarningColor = darken(#ffe564, 35%)
11 | $badgeErrorColor = #DA5961
12 |
13 | // 布局
14 | $navbarHeight = 3.6rem
15 | $sidebarWidth = 20rem
16 | $contentWidth = 740px
17 | $homePageWidth = 960px
18 |
19 | // 响应式变化点
20 | $MQNarrow = 959px
21 | $MQMobile = 719px
22 | $MQMobileNarrow = 419px
23 |
24 | .navbar .logo {
25 | margin-right: 0;
26 | }
27 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # 🤫 前言
2 |
3 | > 本次实战的所有代码、配置已放在 [Github](https://github.com/haixiangyan/linter-toturial "项目 Github") 可尽情把玩。
4 |
5 | 哈喽大家好,我是海怪。
6 |
7 | 不知道大家有没有经常遇到这样一种情况:**每次新建项目做代码风格的配置时总是随便找一篇文章,也不管啥意思,把 `.eslintrc.js` 的配置一抄,再把对应的 NPM 包装上就算完事了。**
8 |
9 | 诶?不想承认?那考考你:`eslint`, `prettier`, `eslint-config-prettier`, `eslint-plugin-prettier`, `prettier-eslint` 这些都是个啥关系?它们的职责是什么?
10 |
11 | 再考考你:如果想用 ESLint 和 TypeScript 结合,要用到哪些包呢?`@typescript-eslint/parser`, `@typescript-eslint/eslint-plugin`, `tslint-plugin-prettier`, `tslint-config-prettier`, `prettier-tslint` 是不是有种“玩排列组合”的感觉?傻傻分不清?
12 |
13 | 
14 |
15 | ## 目的
16 |
17 | 在没深入了解这些工具之前,我也很蒙逼。网上的资源也是东一块西一块的,要不只讲 Prettier,要不只讲 ESLint,
18 | 要不都不讲直接丢一个配置。
19 |
20 | 其实把这些工具单独拆开来看,它们都是很简单的工具。但是由于前端更新速度太快了,导致出现了很多相似的轮子,比如 `tslint` 和 `eslint`,
21 | 而且这些 Linter 一旦和 ES5, ES6 新旧语法、Babel 转译、JSX 等新特性结合,事情就不再简单了。
22 |
23 |
24 | **所以,我决定出一份收敛的教程来说清楚这些工具之间的关系,以及给出日常开发的常用配置。**
25 |
26 | 你可以跟着教程自己手动配置一次,也可以只是瞧瞧看看。不管怎样,我还是希望大家不仅能知其然,也能知其所以然,不要抄个配置文件就走了。
27 | 如果你读完本教程再回头来看自己手头/公司项目的 `.eslintrc.js` 配置时,能够做到一点都不慌,并且心中有数,那么这个教程的目的就达到了。
28 |
29 | ## 内容
30 |
31 | 这篇文章主要分为两部分:
32 |
33 | * **理论篇**:说清楚这些工具的历史和关系,让读者俯瞰整个工具生态
34 | * **实战篇**:从 0 开始配置 Linter,边实战边讲解原理,覆盖范围:ESLint x TypeScript x JavaScript x Babel x JSX x Plugin x Husky x LintStaged x StyleLint
35 |
36 | ## 花絮
37 |
38 | 这篇教程 + 项目是我花了一周的时间弄出来的。
39 |
40 | 实际上我以为只出一篇文章就能讲清楚了,
41 | 后来发现前端 Linter 衍生出来的工具实在是太多了,不弄个项目出个实战教程真的无法解释某些点,然后就出了实战篇。好吧,那就出两篇文章。
42 |
43 | 可是后来随着配置的东西越来越多,踩的坑也变得越来越多,文章的字数快超过 6000 字了,对读者来说不是一个很好的阅读体验,因此拆分了章节,
44 | 最后有了这份教程。
45 |
46 | ## 参考
47 |
48 | 下面列举我写这篇教程所看的一些参考资料,如果大家有兴趣也可以 **按下面给的顺序** 进行阅读:
49 |
50 | * [Prettier看这一篇就行了](https://zhuanlan.zhihu.com/p/81764012) 陈龙大佬的 Prettier 文章,写的非常清楚
51 | * [Prettier 文档](https://prettier.io/docs/en/index.html) 接下来再看 Prettier 文档,结合陈龙大佬的文章会有更高维度的视角
52 | * [ESLint 文档](https://eslint.org/) ESLint 文档非常简陋,像说明书一样,很无聊,但是也要懂一点要怎么配置
53 | * [TypeScript ESLint 文档](https://typescript-eslint.io/) 这个文档远比 ESLint 文档要好,里面还说了一些实践思路,教会你 Config 和 Plugin 都是做啥用的
54 | * [StyleLint 文档](https://stylelint.io/) 经过上面文档的洗礼再看 StyleLint 会觉得很简单
55 | * [Husky Github](https://github.com/typicode/husky) 知道 Husky 是怎么在 Git Hooks 执行 Bash 的
56 | * [lint-staged Github](https://github.com/okonet/lint-staged) 知道 Husky x lint-staged 用法
57 |
58 | 下面再列举一些我搜了很多次的问题:
59 |
60 | ### LintStaged x TypeScript
61 |
62 | * [lint-staged ignores tsconfig.json when it called through husky hooks](https://github.com/okonet/lint-staged/issues/825)
63 | * [Build typescript on commit](https://github.com/okonet/lint-staged/issues/468)
64 | * [How to lint for Typescript compilation issues?](https://stackoverflow.com/questions/51428789/how-to-lint-for-typescript-compilation-issues)
65 | * [Current version incorrectly analyzes @types/node](https://github.com/gustavopch/tsc-files/issues/20)
66 |
67 | ### ESLint
68 |
69 | * ["parserOptions.project" has been set for @typescript-eslint/parser](https://stackoverflow.com/questions/58510287/parseroptions-project-has-been-set-for-typescript-eslint-parser)
70 | * [eslint-plugin-prettier is very slow](https://github.com/prettier/eslint-plugin-prettier/issues/304)
71 | * [Very slow performance](https://github.com/prettier/eslint-plugin-prettier/issues/445)
72 | * [ESLint not reporting TypeScript compiler type checking errors](https://stackoverflow.com/questions/60514929/eslint-not-reporting-typescript-compiler-type-checking-errors)
73 | * [No actual type checking?](https://github.com/typescript-eslint/typescript-eslint/issues/1037)
74 |
75 | ## 支持
76 |
77 | **原创不易,如果觉得本教程对你有帮助,希望大家多多 Star 和 Fork。
78 | 同时我也是个新人公众号号主,如果喜欢我写的内容,也欢迎关注公众号【写代码的海怪】。**
79 |
80 |
81 |
--------------------------------------------------------------------------------
/docs/final/last.md:
--------------------------------------------------------------------------------
1 | # 🙌 结语
2 |
3 | OK,终于大功告成了!这是我写的第二本小书,前一本是 [一天学习一个 npm 轮子](https://github.com/haixiangyan/one-day-one-npm-lib),有兴趣可以看看,海怪又完成了一个小目标。
4 |
5 | **如果喜欢我的文章,也可以关注【写代码的海怪】,每周周五准时更新技术文章,不会太干也不会太水,纯属聊天。**
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/final/project.md:
--------------------------------------------------------------------------------
1 | # 💼 Github 项目
2 |
3 | 如果前面章节里的配置太零散,也可以看 [完整项目](https://github.com/haixiangyan/linter-tutorial) 是怎么配置的。
4 |
--------------------------------------------------------------------------------
/docs/practice/eslint_plugins.md:
--------------------------------------------------------------------------------
1 | # 🔌 ESLint x Plugins
2 |
3 | 上一篇在配置 ESLint x TypeScript 时,我们发现了 Parser 和 Plugin 的规律,下面来聊聊一些常见的 Parser 和 Plugin。
4 |
5 | ## ESLint x Babel
6 |
7 | 在第一篇文章里就说了,可以通过 `env` 来设定 ESLint 的默认 ECMAScript parser 的版本,所以 ESLint 其实是自带有 parser 的,但是它只支持 [最新版的 ECMAScript 标准](https://github.com/eslint/eslint/blob/a675c89573836adaf108a932696b061946abf1e6/README.md#what-about-experimental-features "ESLint parser 支持最新的 ECMAScript 标准")。
8 |
9 | 而 JavaScript 依然在不断发展,时不时又出一些新的 API 或者提案,对于要尝鲜的开发者,ESLint 的 Parser 就解析不动 `.js` 了,因此,我们需要 [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser)。
10 |
11 | ```sh
12 | npm i -D @babel/core @babel/eslint-parser
13 | ```
14 |
15 | 然后添加 `.babelrc.js`,在里面写项目的 babel 规则,这里按你自己需求来就好了,我就不写啦。
16 |
17 | 然后在 `overrides` 里新增对 `.js` 的处理:
18 |
19 | ```js
20 | module.exports = {
21 | env: {
22 | // 支持浏览器环境
23 | browser: true,
24 | // 识别 CommonJS
25 | node: true,
26 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
27 | es2021: true,
28 | },
29 | extends: [
30 | "eslint:recommended", // eslint 自己的推荐规则,最佳实践最小集
31 | "plugin:prettier/recommended", // 禁用 eslint 关于代码的风格的规则,使用 prettier 的风格
32 | ],
33 | overrides: [
34 | // 处理 JS 文件
35 | {
36 | files: ["**/*.{js,jsx}"], // 只处理 js 和 jsx 文件
37 | parser: "@babel/eslint-parser", // 使用 babel 来解析 js 文件
38 | parserOptions: {
39 | sourceType: "module", // 支持 import/export
40 | allowImportExportEverywhere: false,
41 | ecmaFeatures: {
42 | globalReturn: false,
43 | },
44 | },
45 | },
46 | // 处理 TS 文件
47 | {
48 | files: ["**/*.{ts,tsx}"], // 只处理 ts 和 js 文件
49 | parser: "@typescript-eslint/parser", // 能看懂 TypeScript
50 | parserOptions: {
51 | project: ["./tsconfig.json"], // 告诉 eslint:tsconfig 在哪
52 | },
53 | extends: [
54 | // typescript-eslint 的推荐规则,只是这些最佳规则都是针对 TS 的
55 | "plugin:@typescript-eslint/recommended",
56 | // tsconfig.json 里 Type Checking 的推荐规则
57 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
58 | ],
59 | plugins: [
60 | // 使用 typescript x eslint 的插件
61 | "@typescript-eslint",
62 | ],
63 | },
64 | ],
65 | };
66 |
67 | ```
68 |
69 | ## ESLint x React
70 |
71 | 解析 React 文件主要有两大难题:
72 | * 不识别 `import React from 'react'`,会报 `React is not used`
73 | * 不识别 `.jsx`, `.tsx` 文件
74 |
75 | 如果你的项目是 React + TypeScript,那么要在 `tsconfig.json` 里添加对 JSX 的支持:
76 |
77 | ```json
78 | {
79 | ...
80 | "jsx": "react"
81 | }
82 | ```
83 |
84 | 然后在 `overrides` 里也添加对 `.js` 和 `.jsx` 的解析:
85 |
86 | ```js
87 | module.exports = {
88 | overrides: [
89 | {
90 | files: ["**/*.{jsx,js,ts,tsx}"],
91 | ...ESLint x TypeScript 配置
92 | }
93 | ]
94 | }
95 | ```
96 |
97 | 因为 ESLint 这里会 TypeScript 的 parser 结合 `tsconfig.json` 来解析 `.js` 和 `.jsx`,所以 ESLint 是能看懂 `.jsx` 的内容的。
98 |
99 | 那如果项目是 React + JavaScript 呢?或者我就要分开处理 TypeScript 和 JavaScript 呢?我们难道要用 React Parser 么?**No!我们需要的是 ESLint x React 的插件!个人理解 JSX 更属于 JavaScript 的一种特性,而不是语法类型,所以要用 Plugin。**
100 |
101 | 遵循刚刚说的规律,我们安装 [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react):
102 |
103 | ```sh
104 | npm i D eslint-plugin-react
105 | ```
106 |
107 | 最后只需继承它即可,注意这里的 `extends` 大部分时候都可以是 `plugin` 的缩写版本:
108 |
109 | ```js
110 | "extends": [
111 | "eslint:recommended",
112 | "plugin:react/recommended"
113 | ]
114 | ```
115 |
116 | ## ESLint x Vue
117 |
118 | Vue 和 React 同理,它只需要一个 [eslint-plugin-vue](https://eslint.vuejs.org/):
119 |
120 | ```sh
121 | npm i -D eslint-plugin-vue
122 | ```
123 |
124 | 我们依然可以在 `overrides` 中新增一条只针对 `.vue` 文件的配置:
125 |
126 | ```json
127 | overrides: [
128 | // 处理 vue 文件
129 | {
130 | files: ["**/*.vue"], // 只处理 vue 文件
131 | extends: ["plugin:vue/vue3-recommended"], // 使用 vue3 的推荐规则
132 | }
133 | ]
134 | ```
135 |
136 | 这样就可以对所有 `.vue` 文件执行 `eslint '**/*.vue' --fix` 了。
137 |
138 | ## `extends` 和 `plugins` 的区别
139 |
140 | 不知道你有没有在刚刚配置 eslint 的时候,有两个配置项一直很让人迷惑:`extends` 以及 `plugins`。
141 |
142 | 举个例子,我们要配置 eslint x typescript,可以看到官网有这样的配置:
143 |
144 | ```js
145 | module.exports = {
146 | root: true,
147 | parser: '@typescript-eslint/parser',
148 | plugins: [
149 | '@typescript-eslint',
150 | ],
151 | extends: [
152 | 'eslint:recommended',
153 | 'plugin:@typescript-eslint/recommended',
154 | ],
155 | };
156 | ```
157 |
158 | 神奇的是,当你去掉 `plugins` 之后发现 `eslint` 依然可以正常工作。更神奇的是,只要你写了 `extends`,那么连 `parser` 也可以不用加,要知道没有指定 `parser` 选项,eslint 可看不懂你的 TypeScript 文件。
159 |
160 | 所以说,到底是 `plugins` 加上了 TypeScript 的能力还是 `extends` 加上了 TypeScript 的规则呢?真让人头大,直到终于有一天受不了了,翻找了一下网上的资料发现了[这个帖子](https://stackoverflow.com/questions/61528185/eslint-extends-vs-plugins-v2020)。
161 |
162 | 先来说结论吧:**每个 `plugins` 只是开启了这个插件,而 `extends` 则会继承别人写好的一份 `.eslintrc` 的配置,这份配置不仅仅包括了 `rules` 还有 `parser`,`plugins` 之类的东西。**
163 |
164 | 所以回到问题,为什么在继承了 `plugin:@typescript-eslint/recommended` 之后就可以不写 `plugins` 和 `parser` 呢?因为别人已经把配置都放在 `recommended` 这份配置表里了,这样对使用的人来说,就可以少写很多配置项了。
165 |
166 | 也就是说,下面两份配置是等价的:
167 |
168 | ```js
169 | module.exports = {
170 | parser: "@typescript-eslint/parser",
171 | parserOptions: { sourceType: "module" },
172 | plugins: ["@typescript-eslint"],
173 | extends: [],
174 | rules: {
175 | "@typescript-eslint/explicit-function-return-type": [
176 | "error",
177 | {
178 | allowExpressions: true
179 | }
180 | ]
181 | }
182 | }
183 | ```
184 |
185 | 以及
186 |
187 | ```js
188 | module.exports = {
189 | plugins: [],
190 | extends: ["plugin:@typescript-eslint/recommended"],
191 | rules: {
192 | "@typescript-eslint/explicit-function-return-type": [
193 | "error",
194 | {
195 | allowExpressions: true
196 | }
197 | ]
198 | }
199 | }
200 | ```
201 |
202 | 对于第一份配置:
203 | * 需要手动添加 `parser`, `parserOptions`, `plugins`
204 | * 只开启了 `@typescript-eslint/explicit-function-return-type` 一个规则
205 |
206 | 对于第二份配置:
207 | * `plugin:@typescript-eslint/recommended` 自动添加了 `parser`, `parserOptions`, `plugins`
208 | * 一些推荐的 TypeScript ESLint 规则也自动加上了
209 | * 只对 `@typescript-eslint/explicit-function-return-type` 这个规则进行自定义配置
210 |
211 | ## 考考你
212 |
213 | 看到这,你的 `.eslintrc.js` 应该已经写了不少代码了,当然相信你也能慢慢找到 `eslint`, `plugin`, `config`, `prettier`, `parser` 这些关键词之间排列组合的一些规律了。
214 |
215 | 不妨来考考你:
216 | * 我要用 XXX 公司的 ESLint 规则集,应该找哪个名字的 NPM 包?
217 | * 我要解析 YYY 语法,应该搜哪个 NPM 包呢?
218 | * 要屏蔽 XXLint 工具和 Prettier 冲突的规则,应该用哪个 NPM 包?
219 | * 要让处理 `.xxx` 后缀的文件,应该用到哪些 NPM 包呢?
220 |
221 | 如果你对上面的问题都能做到心中有数,那配置 [StyleLint](https://stylelint.io/user-guide/configure/) 就再简单不过了。实际上无论是哪个 xxlint 他们的 NPM 命名规律都是非常类似的。话不多出,马上开始学习下一章吧~
222 |
--------------------------------------------------------------------------------
/docs/practice/eslint_prettier.md:
--------------------------------------------------------------------------------
1 | # ✍️ ESLint x Prettier
2 |
3 | 下面我们来配置 Prettier,它的作用就是格式化代码。先安装依赖:
4 |
5 | ```sh
6 | npm i -D prettier # 安装依赖
7 | ```
8 |
9 | 然后添加 Prettier 的配置文件 `.prettierrc.json`:
10 |
11 | ```json
12 | {}
13 | ```
14 |
15 | **对的,你没看错 Prettier 的基础配置就是 `{}`,啥都没有。**
16 |
17 | 我们日常用的框架和库都可以分为两大类。**第一类叫你是智障(opinionated)**。框架把所有的设计、规范、工具都调到最好,开发者只需要无脑用就可以了,比如 Vue 和 Angular。**第二类叫你是装机猿(unopinionated)**。框架只管自己单一功能,别的需要开发者自己组装,比如 React。
18 |
19 | 这里的 Prettier 的哲学属于 **opinionated**,这个工具研发出来的本身就是为了树立一个最标准的代码风格规范,停止开发者之间的圣战。所以建议如果不是有非常强烈的个人需求,不要在 `.prettierrc.json` 加太多的配置,会有种画蛇添足的感觉。
20 |
21 | 一行命令下去:
22 |
23 | ```sh
24 | npx prettier --write . # 别把这个 . 点漏了
25 | ```
26 |
27 | 就可以看到格式化后的漂亮代码了:
28 |
29 | ```js
30 | // 需要格式化的代码
31 | let x = 1
32 | const hi = 2
33 | const aa = 333
34 | let y = {
35 | name: "Jack",
36 | age: 11,
37 | };
38 |
39 | console.log("result", x, y);
40 |
41 | console.log(hi, aa);
42 | ```
43 |
44 | 如果只想查格式有问题,但是不想格式化代码,可以使用下面命令:
45 |
46 | ```sh
47 | npx prettier --check .
48 | ```
49 |
50 | ## IDE 集成
51 |
52 | 每次都用命令行来修复文件肯定不现实。
53 |
54 | ```sh
55 | npx prettier --write messyJS.js
56 | ```
57 |
58 | 幸好如今 IDE 和一些代码编辑器已经对 Prettier 做了非常好的集成,比如 WebStorm 里:
59 |
60 | 
61 |
62 | 我习惯是不打开【保存时自动格式化】,有点耗内存。另一个选项则是 IDE 会使用 Prettier 的配置来格式化,如果不开启这个选项,那么 IDE 会按自己的风格来格式化,这里推荐开启这个选项。
63 |
64 | 说到这里,相信有的同学已经按耐不住了:IDE 格式化是按 Prettier 配置来了,但是我以前项目经常见的 `ESLint: Fix xxx` 提示咋不出现呢?
65 |
66 | 
67 |
68 | 这个提示是 ESLint 报的提示,下面我们来讲怎么配置 ESLint x Prettier。
69 |
70 | ## ESLint 集成
71 |
72 | 先安装 `eslint-plugin-prettier` 插件:
73 |
74 | ```sh
75 | npm i -D eslint-plugin-prettier
76 | ```
77 |
78 | 在 `.eslintrc.js` 添加 Prettier 插件:
79 |
80 | ```js
81 | module.exports = {
82 | env: {
83 | // 支持浏览器环境
84 | browser: true,
85 | // 识别 CommonJS
86 | node: true,
87 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
88 | es2021: true,
89 | },
90 | extends: "eslint:recommended",
91 | plugins: [
92 | // 使用 Prettier 的代码风格规则
93 | // 并用 Prettier 来自动修复代码
94 | "prettier",
95 | ],
96 | rules: {
97 | // 违反 Prettier 的规则就报 error
98 | "prettier/prettier": "error",
99 | },
100 | };
101 | ```
102 |
103 | `plugins` 里不仅会加载 Prettier 的代码风格规则,还会用 Prettier 来自动修复违反代码风格规则的代码。
104 |
105 | `rules` 里的配置表示违反 Prettier 规则会报 Error 错误,如果你改成 "warn" 就展示为 warning 样式。
106 |
107 | 加完配置后就会发现代码已经有对应的自动修复提示了:
108 |
109 | 
110 |
111 | **在 ESLint 集成了 Prettier 后,在在命令行里执行 `eslint xxx.js --fix`,
112 | 那么 ESLint 就会调用 Prettier 的格式化功能来帮你格式化代码了。**
113 |
114 | ## 解决规则冲突
115 |
116 | 既然 Prettier 有自己代码风格,ESLint 里也有代码风格,难免会出现规则之间的冲突,比如 [在这个 Issue 里](https://github.com/prettier/eslint-plugin-prettier/issues/65 "规则冲突 Issue") 就说了在同时自动修复 `arrow-body-style` 和 `prefer-arrow-callback` 规则时,自动修复后的代码会少了个括号:
117 |
118 | ```js
119 | // 原来的代码
120 | function foo() {
121 | return isTrue && [0,1,2].map(function(num) {
122 | return num * 2;
123 | });
124 | }
125 | ```
126 |
127 | ```js
128 | // 格式化后的代码少了一个括号
129 | function foo() {
130 | return (
131 | isTrue &&
132 | [0, 1, 2].map((num) => {
133 | return num * 2;
134 | });
135 | // 这里少了一个右圆括号
136 | }
137 | ```
138 |
139 | 解决冲突的最好方法就是把对应的 rule 给禁用了:
140 |
141 | ```json
142 | {
143 | "rules": {
144 | "arrow-body-style": "off",
145 | "prefer-arrow-callback": "off"
146 | }
147 | }
148 | ```
149 |
150 | 为了解决ESLint配置可能与Prettier产生的冲突,利用[eslint-config-prettier](https://github.com/prettier/eslint-config-prettier)来关闭相关的配置。
151 |
152 | > Turns off all rules that are unnecessary or might conflict with Prettier.
153 | > This lets you use your favorite shareable config without letting its stylistic choices get in the way when using Prettier.
154 | > Note that this config only turns rules off, so it only makes sense using it together with some other config.
155 |
156 |
157 | ```sh
158 | npm i -D eslint-config-prettier
159 | ```
160 |
161 | `eslint-plugin-prettier` 库还提供了一种缩写版的配置:直接在 `.eslintrc.js` 中写一行配置即可完成 ESLint x Prettier 的所有配置。
162 |
163 | ```js
164 | module.exports = {
165 | env: {
166 | // 支持浏览器环境
167 | browser: true,
168 | // 识别 CommonJS
169 | node: true,
170 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
171 | es2021: true,
172 | },
173 | extends: [
174 | "eslint:recommended",
175 | "plugin:prettier/recommended"
176 | ],
177 | };
178 | ```
179 |
180 | 它等同于:
181 |
182 | ```js
183 | {
184 | env: {
185 | // 支持浏览器环境
186 | browser: true,
187 | // 识别 CommonJS
188 | node: true,
189 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
190 | es2021: true,
191 | },
192 | // 继承 Prettier 格式化规则
193 | extends: [
194 | "eslint:recommended",
195 | "prettier"
196 | ],
197 | // 使用 Prettier 格式化
198 | plugins: ["prettier"],
199 | // 解决规则冲突
200 | rules: {
201 | "prettier/prettier": "error",
202 | "arrow-body-style": "off",
203 | "prefer-arrow-callback": "off"
204 | }
205 | }
206 | ```
207 |
208 | 至于为啥规则集合需要去单独设置 `arrow-body-style` 和 `prefer-arrow-callback`, 参考这个 [issue](https://github.com/prettier/eslint-plugin-prettier/issues/65)
209 |
210 | 上一篇里说到看到 `eslint-config-xxx` 就相当于看到了 xxx 的规则集。这里也同样适用:**`eslint-config-prettier` 可以看成 Prettier 的代码风格规则集。只不过这里的作用是禁用掉一些和 Prettier 有冲突的规则。**
211 |
212 | 现在我们不妨脑洞大开一下:那既然后缀的 `xxx` 可以改,那前缀的 `eslint` 是否也可以改呢,是否也有一些 `xxx-config-prettier` 呢?**猜得不错,除了它,还有俩:**
213 |
214 | * [tslint-config-prettier](https://github.com/prettier/tslint-config-prettier): 禁用和 Prettier 冲突的 TSLint 规则
215 | * [stylelint-config-prettier](https://github.com/prettier/stylelint-config-prettier): 禁用和 Prettier 冲突的 StyleLint 规则
216 |
217 | 不过呢,也仅有这两个库了。说明我们的 Prettier 之旅还没结束呢!接下来我们顺着 ESLint x TypeScript 来继续下一篇文章吧~
218 |
--------------------------------------------------------------------------------
/docs/practice/eslint_typescript.md:
--------------------------------------------------------------------------------
1 | # 👮♂️ ESLint x TypeScript
2 |
3 | 欢迎回来!
4 |
5 | 如今 2022 年,是个人都会用上 TypeScript,下面就来聊聊 ESLint x TypeSciprt。
6 |
7 | ## 再无 TSLint
8 |
9 | **首先想说的是 TSLint 在 2019 年已经凉凉了,取而代之的是 [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) 两个 NPM 包。**
10 |
11 | 我们先来安装一下:
12 |
13 | ```sh
14 | # TypeScript
15 | npm install -D typescript
16 | # TypeScript 解析器
17 | @typescript-eslint/parser
18 | # TypeScript 一些 Lint 规则和功能
19 | @typescript-eslint/eslint-plugin
20 | ```
21 |
22 | 然后在 `src` 下创建一个 `./src/messyTS.ts` 文件,在里面写一些乱七八糟的代码:
23 |
24 | ```ts
25 | const x = 1
26 |
27 | const y = {
28 | name: "Jack",
29 | age: 11,
30 | };
31 |
32 | console.log("z" ,
33 | x,
34 | y
35 |
36 | );
37 |
38 | const hello: Hello = {
39 | name:
40 |
41 | 'zzz',
42 | age: 1
43 | }
44 |
45 | console.log(hello);
46 | ```
47 |
48 | 同样,再加一个很乱的类型声明文件 `./src/messyTypes.d.ts`:
49 |
50 | ```ts
51 | type Hello = {
52 | name: string; age:number
53 | }
54 | ```
55 |
56 | 再用下面命令创建一个 `tsconfig.json`,没有它是无法在 IDE 里做自动类型检查的:
57 |
58 | ```json
59 | npx tsc --init
60 | ```
61 |
62 | 加完这些配置之后会发现,ESLint 并不认识 `.ts` 文件:
63 |
64 | 
65 |
66 | 受前面 `env` 配置的影响,有的同学可能会想:是不是在 `env` 里加个 `typescript: true` 就可以了呢?
67 |
68 | **答案是:不行,TypeScript 不算是 “环境”,而是语法,我们需要的是一个 TypeScript 的解析器,上面的 [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) 正是帮助 ESLint 理解 TypeScript 语法的 Parser。而 [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) 则能提供一些 TS 的 Lint 规则和功能。配置如下:**
69 |
70 | ```js
71 | module.exports = {
72 | env: {
73 | // 支持浏览器环境
74 | browser: true,
75 | // 识别 CommonJS
76 | node: true,
77 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
78 | es2021: true,
79 | },
80 | parser: "@typescript-eslint/parser", // 能看懂 TypeScript
81 | parserOptions: {
82 | project: ["./tsconfig.json"], // 告诉 eslint:tsconfig 在哪
83 | },
84 | plugins: ["@typescript-eslint"],
85 | extends: [
86 | "eslint:recommended",
87 | "plugin:prettier/recommended"
88 | ],
89 | };
90 | ```
91 |
92 | 配置完了之后,你会发现在 `.eslintrc.js` 会报一个这样的错误:
93 |
94 | 
95 |
96 | ```
97 | Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
98 | ```
99 |
100 | **这个错误是因为:既然你默认规定 ESLint 要 lint `.eslintrc.js`,可是 `tsconfig.json` 里又没有 include 这个文件。导致 ESLint 想 Lint `.eslintrc.js` 的时候,发现它又不在 Parser 的 `tsconfig.json` 名单列表上,就蒙逼了。**
101 |
102 | 最简单的解决方法就是在在 `tsconfig.json` 里 include 一下就 OK 了:
103 |
104 | ```json
105 | {
106 | "compilerOptions": {...},
107 | "include": [".eslintrc.js", "src"],
108 | "exclude": ["node_modules"]
109 | }
110 | ```
111 |
112 | 或者在 `.eslintignore` 里添加该文件,将其忽略掉:
113 |
114 | ```
115 | # 忽略 .eslintrc.js
116 | .eslintrc.js
117 | ```
118 |
119 | **但是这两都不是很好的解决方法,因为除了 `src` 目录,正常项目中还会有很多其它普通的 `.js` 文件,比如 `.babelrc.js`, `webpack.config.js`, `start.js` 等配置文件。我们既想要 ESLint x Prettier 格式化这些文件,但又不想将它们放到 `tsconfig.json` 里做类型检查。**
120 |
121 | ESLint 正好提供了一个 `overrides` 配置来解决这样的问题:
122 |
123 | ```js
124 | module.exports = {
125 | env: {
126 | // 支持浏览器环境
127 | browser: true,
128 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
129 | es2021: true,
130 | // Linter .eslintrc.js 自己的时候要用 node 环境
131 | node: true,
132 | },
133 | extends: [
134 | "eslint:recommended",
135 | "plugin:prettier/recommended"
136 | ],
137 | overrides: [
138 | // 处理 TS 文件
139 | {
140 | files: ["**/*.{ts,tsx}", "**/*.{js,jsx}"], // 只处理 ts 和 js 文件
141 | excludedFiles: [".eslintrc.js"], // 这里禁用了 .eslintrc.js 的类型检查
142 | parser: "@typescript-eslint/parser", // 能看懂 TypeScript
143 | parserOptions: {
144 | project: ["./tsconfig.json"], // 告诉 eslint:tsconfig 在哪
145 | },
146 | extends: [
147 | // typescript-eslint 的推荐规则,只是这些最佳规则都是针对 TS 的
148 | "plugin:@typescript-eslint/recommended",
149 | // tsconfig.json 里 Type Checking 的推荐规则
150 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
151 | ],
152 | plugins: [
153 | // 使用 typescript x eslint 的插件
154 | "@typescript-eslint",
155 | ],
156 | },
157 | ],
158 | }
159 | ```
160 |
161 | 上面的配置把 TypeScript 的 ESLint 配置归为一类,并只对 ``"**/*.{ts,tsx}", "**/*.{js,jsx}"`` 进行 Lint 操作,同时将 `.eslintrc.js` 排除掉。**这样一来,ESLint 不仅可以针对这类文件来做 Linter 检查,开发者也不用把冗余的 `.js` 文件放到 `tsconfig.json` 里了。**
162 |
163 | 到此为止,你的 ESLint x TypeScript 已经完成了。
164 |
165 | ## 命名规律
166 |
167 | 通过上面的 ESLint x TypeScript 配置,我们不难发现 ESLint 的又一大规律:
168 |
169 | * 当 ESLint 无法解析文件时,比如看不懂新语法时,就可以搜索 "ESLint xxx Parser" 来找对应的 NPM 包
170 | * 当 ESLint 要 Lint 一些非 `.js` 文件时,可以搜索 "ESLint xxx plugin" 来解决
171 |
172 | 那下一篇就来聊聊 ESLint 的一些常用的 Plugin 吧。走起~
173 |
--------------------------------------------------------------------------------
/docs/practice/hello_eslint.md:
--------------------------------------------------------------------------------
1 | # 👋 Hello ESLint
2 |
3 | 首先,我们来配置一下 ESLint,在 `src` 下创建一个 `./src/messyJS.js` 文件:
4 |
5 | ```js
6 | // 需要报 for-direction Linter error
7 | for (let i = 0; i < 10; i--) {
8 | console.log(i);
9 | }
10 |
11 | // 需要报 max-len Linter error
12 | const longFunction = (somethingVeryLong1) => {console.log(1)}
13 | longFunction(1, 2, 3, 4, 5);
14 |
15 | // 需要格式化的代码
16 | let x = 1;
17 | const hi = 2
18 | const aa = 333;
19 | let y = {
20 | name: 'Jack', age: 11 }
21 |
22 | console.log('result'
23 | ,x,
24 | y)
25 |
26 | console.log(hi, aa)
27 | ```
28 |
29 | 接下来安装 `eslint`:
30 |
31 | ```sh
32 | npm i -D eslint
33 | ```
34 |
35 | ## IDE 集成
36 |
37 | 现在很多 IDE 的插件都和 ESLint 配合得非常好了:
38 |
39 | 
40 |
41 | > **温馨提示:下面在配置 eslint 过程中,如果会出现 “他奶奶地,我明明配置好了,为什么不生效?”的问题,你可以多刷新一下 ESLint(Disable 再 Enable)来使其生效。**
42 |
43 | ## 指定环境
44 |
45 | 回到项目,我们新建一个 `.eslintrc.js` 配置文件,里面什么都不写:
46 |
47 | ```js
48 | module.exports = {};
49 | ```
50 |
51 | 这里就体现了 ESLint 一个非常好的设计:**解耦**。如果里面什么都不配置,那么默认啥规都不会有,当然,也看不懂你的代码......
52 |
53 | 
54 |
55 | 所以需要添加一个 `env` 来告诉 ESLint 现在你的代码要在什么场景下跑:
56 |
57 | ```js
58 | module.exports = {
59 | env: {
60 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
61 | es2021: true,
62 | }
63 | };
64 | ```
65 |
66 | 设置了 `env` 之后,ESLint 就会识别这个场景下一些预设好的变量,比如 jQuery 的 `$`,CommonJS 里的 `module` 等。
67 |
68 | 其它具体环境可以看官网的 [Environments 章节](https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments),里面介绍了 ESLint 在不同环境下的 key 是啥:
69 |
70 | 
71 |
72 | 其中的 `esxxxx` 环境会让 ESLint 除了添加预设好的 20xx 全局变量外,它还会自动设定 `ecmaVersion` parser 版本,让对应的 parser 来解析代码:
73 |
74 | ```js
75 | module.exports = {
76 | env: {
77 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
78 | es2021: true,
79 | }
80 | }
81 | ```
82 |
83 | 配置完了就发现 `let` 可以正常解析了。
84 |
85 | ## 规则(集)
86 |
87 | 同样的,如果不配置任何规则,那么 ESLint 不会报任务警告和错误。我们可以在 `rules` 里去配置自己想要 lint 的规则:
88 |
89 | ```js
90 | module.exports = {
91 | env: {
92 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
93 | es2021: true,
94 | },
95 | rules: {
96 | // 每行不能超过 80 个字符
97 | "max-len": ["error", { "code": 80 }],
98 | },
99 | };
100 | ```
101 |
102 | 配置完后就可以看到 ESLint 的报错信息了:
103 |
104 | 
105 |
106 | 可是,如果要我一个个规则来配,那不得配到天荒地老?
107 |
108 | **ESLint 给出的解决方案是 “规则集”。公司部门或者社区会制定一些规则集,开发者只需要在 `.eslintrc.js` 里添加一行代码就能继承这些规则了。**
109 |
110 | 在安装 `eslint` 后,默认就有一个装一送一的 `eslint:recommended` 规则集:
111 |
112 | ```js
113 | module.exports = {
114 | env: {
115 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
116 | es2021: true,
117 | },
118 | // 继承 ESLint 的规则集
119 | extends: "eslint:recommended",
120 | };
121 | ```
122 |
123 | 添加了这个规则集之后,你会发现 `.eslintrc.js` 的 `module.exports` 报错了:
124 |
125 | 
126 |
127 | 这里的 `no-undef` 正是该规则集里的其中一条规则。ESLint 会默认处理所有的 `.js`,所以当处理自身 `.eslintrc.js` 配置文件时,它会按普通 JS 来阅读,无法看懂 `module` 从哪来的。
128 |
129 | 记不记得刚刚我们说要用 `env` 来让 ESLint 识别对应环境的语法?所以这里的 `env` 要加上 `node: true`,这样就可以让 ESLint 看懂 CommonJS 啦,为了能覆盖浏览器场景,我们也可以把 `"browser": true` 给打开。
130 |
131 | ```js
132 | module.exports = {
133 | env: {
134 | // 支持浏览器环境
135 | browser: true,
136 | // 识别 CommonJS
137 | node: true,
138 | // 识别 ES 的代码,使用 ECMAScript 2021 自动设置 ecmaVersion parser 为 12,
139 | es2021: true,
140 | },
141 | // 继承 ESLint 的规则集
142 | extends: "eslint:recommended",
143 | }
144 | ```
145 |
146 | 回到刚说的 “规则集” 话题。目前最出名的规则集应该是 Airbnb 制定的 ESLint 规则集 [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb)。
147 |
148 | **ESLint 的规则集一般以名为 `eslint-config-xxx` 的方式命名。**
149 |
150 | 所以,以后大家只要看到 `eslint-config-xxx` 名字的 NPM 包,就知道它是 ESLint 的规则集了,或者当你要查某公司/工具/社区的规则集时,也可以通过 "eslint config xxx" 来搜索。
151 |
152 | ## 规则分类
153 |
154 | ESLint 的规则分为两类:
155 | * **Formatting rules: 代码风格规则**。比如:max-len, no-mixed-spaces-and-tabs, keyword-spacing, comma-style
156 | * **Code-quality rules: 代码质量规则**。比如:no-unused-vars, no-extra-bind, no-implicit-globals, prefer-promise-reject-errors
157 |
158 | 让我们回到项目。加了这些规则集之后,你会发现 **ESLint 报错的规则大多数是属于无法自动修复的代码质量规则**,比如这里的 `no-unused-vars`:
159 |
160 | 
161 |
162 | ESLint 挠破头皮也猜不出来你写个没有用到的变量是为了啥,所以只能人工手动去修。
163 |
164 | 我们常说的 `ESLint src --fix` 其实更多指代代码风格的修正,不过 ESLint 在格式化方面比较弱,真正的格式化高手是 [Prettier](https://prettier.io)。
165 |
166 | 下一篇就来讲讲 [ESLint x Prettier](./eslint_prettier),看看 ESLint 和 Prettier 能碰撞出多少火花,这将非常有趣。
167 |
--------------------------------------------------------------------------------
/docs/practice/husky_lint-staged.md:
--------------------------------------------------------------------------------
1 | # 🐶 Husky x LintStaged
2 |
3 | 上一章说到我们不能保证所有人提交的代码都是 fix 好的。
4 | 比如有的人经常忘记开启 ESLint 插件,提交代码时还以为自己代码写的贼 6,没啥报错,但到队友那 `git pull` 满屏都是红的。
5 | 所以,更好的做法是在代码入库(Commit 和 Push)的时候可以做一次 ESLint 的检查。
6 |
7 | 正好 Git 提供了很多 [Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks):
8 |
9 | * pre-commit: 提交代码前做一些事
10 | * pre-push: 推代码前做一些事
11 | * pre-merge: 合并代码前做一些事
12 | * pre-rebase: rebase 前做一些事
13 | * ...
14 |
15 | 这些 Hooks 可以使得我们在操作 Git 的某些阶段做一些事情。 [Husky](https://github.com/typicode/husky) 可以在这些 Git Hooks 回调时执行我们定义好的 Bash 脚本。
16 | 如果我们把 ESLint 的修复命令放在这些的 Bash 脚本中,那就可以实现 Git Commit/Push/Merge/... 前的 ESLint 自动修复了!
17 |
18 | )
19 |
20 | **注意:Husky v4 和 v7 有非常大的差异,大家一定要注意甄别,最好直接看官网,这里使用最新版跟大家讲解。**
21 |
22 | ```sh
23 | # 安装哈士奇
24 | npm install husky -D
25 | # 添加 prepare 命令
26 | npm set-script prepare "husky install"
27 | # prepare 创建 bash 脚本,安装 git hooks
28 | npm run prepare
29 | # 添加 pre-commit 的 git hook 脚本
30 | npx husky add .husky/pre-commit "npx eslint src --fix"
31 | ```
32 |
33 | 运行之后会发现在 `./.husky/pre-commit` 里看到 `git commit` 前会运行的脚本:
34 |
35 | ```sh
36 | #!/bin/sh
37 | . "$(dirname "$0")/_/husky.sh"
38 |
39 | # git commit 前先 eslint fix 一波
40 | npx eslint src --fix
41 | ```
42 |
43 | **但是这样的命令会让每次 commit 前都把整个 `src` 都扫描并 fix 一次,速度太慢了,而且很容易把别人的屎山也 fix 掉,然后提交上去。**
44 |
45 | 我们更希望只针对提交的文件进行 Lint 操作。
46 |
47 | ## LintStaged
48 |
49 | Prettier 在 [文档的 Pre-commit Hook](https://prettier.io/docs/en/precommit.html) 已经介绍了很多只针对提交文件做 fix 的工具。这里以 [lint-staged](https://github.com/okonet/lint-staged) 做介绍。
50 |
51 | ```sh
52 | # 安装
53 | npm i -D lint-staged
54 | ```
55 |
56 | 然后添加 `.lintstagedrc.js` 配置文件,里面对提交不同的文件进行 `eslint --fix` 操作。
57 |
58 | ```js
59 | module.exports = {
60 | '**/*.{ts,tsx,js,jsx}': [
61 | "eslint --cache --fix",
62 | ],
63 | "**/*.vue": [
64 | "eslint --cache --fix",
65 | ],
66 | "**/*.{css,less}": [
67 | "stylelint --cache --fix",
68 | ]
69 | }
70 | ```
71 |
72 | `lint-staged` 配置的含义是对提交上来不同类型的文件执行对应的 lint fix 命令。
73 |
74 | 最后在刚刚创建的 `./.husky/pre-commit` 里改成执行 `lint-staged` 命令:
75 |
76 | ```bash
77 | #!/bin/sh
78 | . "$(dirname "$0")/_/husky.sh"
79 |
80 | npx lint-staged
81 | ```
82 |
83 | 以后每次 commit 前都会跑一次 `lint-staged`,而 `lint-staged` 又会对提交的文件进行 ESLint Fix。
84 |
85 | ## 命令行
86 |
87 | 如果细心的同学会发现上面提到关于 `eslint` 的自动修复命令一共有两条:`eslint src --fix` 以及 `eslint --cache --fix`。
88 |
89 | 如果你直接在命令行里跑 `eslint --fix`,那什么事都不会发生,因为你没有指定要 fix 的文件以及文件目录。
90 |
91 | 那为什么在 `lint-staged` 里就可以 `eslint --cache --fix` 呢?
92 |
93 | **因为 `lint-staged` 会把前面的 `*.{js,jsx,ts,tsx}` 来匹配提交的文件,并把它们作为参数传到 `eslint --cache --fix` 后面。所以虽然写的是 `eslint --cache --fix` 时实际上是执行了 `eslint 要修复的文件 --cache --fix`。**
94 |
95 | ## 性能问题
96 |
97 | 或许有的同学会发现每次 `eslint --fix` 的时候跑的有点慢,如果你在前面加一个 `TIMING=1`:
98 |
99 | ```shell
100 | TIMING=1 eslint src --fix
101 | ```
102 |
103 | 就可以看到哪个规则跑了多久:
104 |
105 | ```shell
106 | Rule | Time (ms) | Relative
107 | :---------------------------------------|----------:|--------:
108 | prettier/prettier | 207.532 | 79.8%
109 | @typescript-eslint/no-unused-vars | 12.738 | 4.9%
110 | @typescript-eslint/no-floating-promises | 8.053 | 3.1%
111 | @typescript-eslint/no-unsafe-assignment | 7.509 | 2.9%
112 | vue/attributes-order | 7.424 | 2.9%
113 | no-unused-vars | 1.977 | 0.8%
114 | no-redeclare | 1.539 | 0.6%
115 | react/display-name | 1.219 | 0.5%
116 | no-global-assign | 0.873 | 0.3%
117 | @typescript-eslint/no-unsafe-argument | 0.795 | 0.3%
118 | ```
119 |
120 | 毕竟 `prettier/prettier` 是一大堆的 Prettier 代码风格规则,所以肯定是跑得慢的。
121 | 当然也有很多人会很在意这个点,所以也有了 [这个 Issue](https://github.com/prettier/eslint-plugin-prettier/issues/304) 。
122 |
123 | 不过这个 Issue 也没能给出太好的解决方案,如果你有更好的方案可以 [在这里提 Issue](https://github.com/haixiangyan/linter-tutorial/issues) 。
124 |
125 | ## LintStaged x TypeScript
126 |
127 | 你以为到这就完了么?Too yong too simple!如果你在 `.d.ts` 定义一个 `interface`:
128 |
129 | ```ts
130 | type Hello = {
131 | name: string;
132 | age: number;
133 | };
134 | ```
135 |
136 | 然后在另一个 `.ts` 里错误地使用它:
137 |
138 | ```ts
139 | // 注意:这里没有 import Hello 是正常的,因为 Hello 是在 .d.ts 里定义
140 | const hello: Hello = {
141 | name: "xx",
142 | age: 11,
143 | xxx: 11, // Error
144 | };
145 | ```
146 |
147 | 
148 |
149 | **然后直接强行 `git add ./`, `git commit -m 'update'`,发现竟然可以直接通过而不报错!**
150 |
151 | 不报错的原因是因为:**ESLint 本身就不会做类型校验(Type Check)。** 理由如下(具体可见 [这个 Issue](https://github.com/typescript-eslint/typescript-eslint/issues/1037#issuecomment-537608227)):
152 |
153 | * ESLint 只是作为 TypeScript Type Checking 的补充,只做 Type Checking 之外的一些工作
154 | * 大多数人用 TS 的 Parser,但是不用 `parserOptions.project`,所以这种情况下也不能 Type Check
155 | * 和 TypeScript 相对完整的错误校验上报体系相比,ESLint 只完成了一半的工作
156 |
157 | 总的来说就是你用 `tsc --noEmit` 就能做类型检查,ESLint 就不用再重复造一次轮子了,再看看隔离 Babel 大哥,它就是转译器,它也不做 TS 的语法校验呀,还是一个工作做一件事的好。
158 |
159 | 
160 |
161 | 有些同学估计都会抢答了:我知道我知道,直接在 `.lintstagedrc.js` 里添加一行 `tsc` 不就完事了?
162 |
163 | ```js
164 | module.exports = {
165 | '**/*.{ts,tsx}': [
166 | "tsc", // 检查 TypeScript
167 | "eslint --cache --fix",
168 | ],
169 | '**/*.{js,jsx}': [
170 | "eslint --cache --fix",
171 | ],
172 | "**/*.vue": [
173 | "eslint --cache --fix",
174 | ],
175 | "**/*.{css,less}": [
176 | "stylelint --cache --fix",
177 | ]
178 | }
179 | ```
180 |
181 | **年轻人,依然 Too young too naive!让我们走进下一章,看看 `tsc` 会给我们带来什么样的难题吧。**
182 |
--------------------------------------------------------------------------------
/docs/practice/lint-staged_tsc.md:
--------------------------------------------------------------------------------
1 | # 🤥 LintStaged x TSC
2 |
3 | 我相信大多数人是不太了解 `tsc`。`tsc` 本身是一个编译工具,它主要工作是将 `.ts` 转换为 `.js` 文件,但是大多数项目在打包时都会用 `babel` 来处理了,所以也就没 `tsc` 什么事了,大家也很少会用到。
4 |
5 | 回到我们项目,刚刚的配置为什么有问题呢?
6 |
7 | ```js
8 | module.exports = {
9 | '**/*.{ts,tsx}': [
10 | "tsc", // 检查 TypeScript
11 | "eslint --cache --fix",
12 | ],
13 | '**/*.{js,jsx}': [
14 | "eslint --cache --fix",
15 | ],
16 | "**/*.vue": [
17 | "eslint --cache --fix",
18 | ],
19 | "**/*.{css,less}": [
20 | "stylelint --cache --fix",
21 | ]
22 | }
23 | ```
24 |
25 | ## tsc 的参数
26 |
27 | 这里单单一个 `tsc` 是不够的,因为我们需要的是只检查类型,但不输出,所以要加一个 `--noEmit` 参数,同时也不要去检查 node_modules 里的类型,要加 `--skipLibCheck` 参数。完整的命令为 `tsc --noEmit --skipLibCheck`。
28 |
29 | ```js
30 | module.exports = {
31 | '**/*.{ts,tsx}': [
32 | "tsc --noEmit --skipLibCheck", // 检查 TypeScript
33 | "eslint --cache --fix",
34 | ],
35 | ...
36 | }
37 | ```
38 |
39 | 但是如果我们有这样的 `.ts` 文件:
40 |
41 | ```ts
42 | // messyTS.ts
43 | const hello: Hello = {
44 | name: 'hi'
45 | }
46 | ```
47 |
48 | 以及对应的 `.d.ts` 类型声明文件:
49 |
50 | ```ts
51 | // messyTypes.d.ts
52 | interface Hello {
53 | name: string;
54 | }
55 | ```
56 |
57 | 然后我们 **只在 `messyTS.ts` 做了改动并提交,** 这条命令在 `lint-staged` 调用时会报下面的错误:
58 |
59 | 
60 |
61 | 报错里说的是找不到 `Hello` 这个 interface。但是我们在写项目的时候,IDE 都会自动找到这个类型声明文件的呀,为什么这样就不行了呢?
62 |
63 | 这是因为 IDE 会自动读取读 `tsconfig.json` 文件,而这里 `tsc` 命令没有读取 `tsconfig.json` 导致找不到 `Hello` 这个 interface。那么,很自然我们就会想是否可以 `tsc -p tsconfig.json --noEmit --skipLibCheck` 这样写呢?**抱歉,依然报错:**
64 |
65 | 
66 |
67 | **他奶奶地!为什么会报错?!**
68 |
69 | 
70 | 这是因为 `tsc` 只有两种调用方式:
71 |
72 | * `tsc -p tsconfig.json`:直接加载 `tsconfig.json` 时,会编译 `tsconfig.json` 里 `include` 的文件
73 | * `tsc xxx.ts`:直接编译命令行里写的 TS 文件,但是会自动忽略 `tsconfig.json`
74 |
75 | **这里因为 `lint-staged` 会把提交的文件作为参数传给 `tsc` 命令,实际执行的命令是 `tsc xxx.ts -p tsconfig.json --noEmit --skipLibCheck`,所以就会出现又要加载 `tsconfig.json` 编译 `include` 的 TS 文件,又要单独编译 `**/*.ts` 的文件,`tsc` 就蒙圈了。**
76 |
77 | 这个问题也在 `lint-staged` 的 [这个 Issue: Allow tsconfig.json when input files are specified](https://github.com/microsoft/TypeScript/issues/27379 "lint-staged 中使用 tsc 的问题") 中有提到。里面对如何解决这样的冲突讨论的非常激烈。其中有一位大哥想了一个方法:**我把 tsconfig.json 的 JSON 拿出来,再把里面的 key-value 对转化成 --xxx 的 bash 参数不就算加载了 tsconfig.json 了么?最后,他造了一个轮子 [tsc-files](https://github.com/gustavopch/tsc-files#readme)。**
78 |
79 | ## tsc-files 的问题
80 |
81 | 然而问题依然存在,因为我们一般在 `tsconfig.json` 里都会把 `src` 放在 `include` 里:
82 |
83 | ```json
84 | {
85 | "include": ["src"],
86 | "exclude": ["node_modules"]
87 | }
88 | ```
89 |
90 | **这样一来,运行 `tsc-files --noEmit` 就会扫描整个 `src` 的 `.ts` 文件,无法达到 `lint-staged` 的目的了。**
91 |
92 | 所以 `tsc-files` 在 `v1.1.3` 这个版本会把 `include` 设置成空数组 `[]`,然后把 `lint-staged` 的文件放在 `files: ["xxx.ts"]`。
93 |
94 | 但是这又回到刚刚无法检测 `messyTypes.d.ts` 里 `Hello` interface 的问题,因为 `messyTypes.d.ts` 没有被放到 `files` 中:
95 |
96 | 
97 |
98 | 这个问题在 [这个 Issue: Current version incorrectly analyzes @types/node](https://github.com/gustavopch/tsc-files/issues/20 "tsc-files 问题") 中又又又被疯狂讨论。
99 | 里面提出了一个想法:把 `typeRoots` 的路径放到 `include` 里,这样就可以用 `typeRoots` 自定义类型声明文件的路径来检测所有的 `.d.ts` 了,但是这还是有问题,具体看下面这段:
100 |
101 | 
102 |
103 | > deanolium 的观点是:如果把 `typeRoots` 放在 `include` 里,我们不能保证所有人都会用 `tsconfig.json` 里的 `typeRoots`,因为不是所有人都是配置大神。
104 | > 如果要在 `typeRoots` 里写自定义类型声明文件目录,那就要手动加上 `./node_modules/@types` 目录,不然不会自动 import node_modules 里的 `.d.ts`。
105 | > 而且如果大家不了解 `tsc-files` 的原理和实现,根本就不知道有这个坑。`tsc-files` 升级版本后还需要用户手动去改 `tsconfig.json` 并不是一个好的实践。
106 |
107 | > gustavopch(作者)的观点是:一方面使用 `tsc-files` 时不应该加上所有的文件,因为这会扫描整个项目,就违反 `lint-staged` 使用的初衷了。
108 | > 另一方面就算 `include` 里能读取 `typeRoots` 目录也不能保证能自动检测到所有类型,因为有的人可能会在 `.ts` 也用 `declare` 来定义,也会有坑。
109 |
110 | 累了,毁灭吧。
111 |
112 | 
113 |
114 | ## 我的方案
115 |
116 | 总的来说,要么扫描 `src` 里的所有 `.ts` 做类型检查,要么只扫描 Git 提交的文件,但是会报找不到类型的错误。
117 |
118 | 很抱歉,目前我能找到的资料都没有很好的解决方案,如果你有更好的 LintStaged x TypeScript 配置方案,可以 [提 Issue](https://github.com/haixiangyan/linter-tutorial/issues)。
119 |
120 | 不过我自己也想到了一个方法就是显式扫描 `.d.ts`。
121 |
122 | ```js
123 | const declarationFiles = [
124 | './src/messyTypesInfo.d.ts'
125 | ]
126 |
127 | module.exports = {
128 | '**/*.{ts,tsx}': [
129 | (filenames) => {
130 | const files = [...filenames, ...declarationFiles];
131 | return `tsc-files ${files.join(' ')} --noEmit --skipLibCheck`;
132 | },
133 | "eslint --cache --fix",
134 | ],
135 | '**/*.{js,jsx}': [
136 | "eslint --cache --fix",
137 | ],
138 | "**/*.vue": [
139 | "eslint --cache --fix",
140 | ],
141 | "**/*.{css,less}": [
142 | "stylelint --cache --fix",
143 | ]
144 | }
145 | ```
146 |
147 | 或者用 `fs` 模块来读取项目中 `./src/typings` 下的所有 `.d.ts` 声明文件,然后再放到命令中。
148 |
149 | 要么也可以在每次 Commit 前全面扫描:
150 |
151 | ```js
152 | module.exports = {
153 | "**/*.{ts,tsx}": [
154 | () => "tsc -p tsconfig.json --noEmit",
155 | "eslint --cache --fix",
156 | ],
157 | "**/*.{js,jsx}": [
158 | "eslint --cache --fix",
159 | ],
160 | "**/*.vue": [
161 | "eslint --cache --fix",
162 | ],
163 | "**/*.{css,less}": ["stylelint --cache --fix"],
164 | };
165 | ```
166 |
167 | 缺点是 pre-commit 的时候会慢一点。
168 |
--------------------------------------------------------------------------------
/docs/practice/stylelint.md:
--------------------------------------------------------------------------------
1 | # 🧜♀️ StyleLint
2 |
3 | 虽然前面那么多篇文章讲得都是 ESLint 的内容,但其实都是给大家做铺垫,让大家能不仅能知其然,还能知其所然。
4 |
5 | ESLint 再强大也只是 JavaScript 和 TypeScript 的 Linter,对样式文件 `.css`, `less`, `scss` 则需要 [StyleLint](https://stylelint.io/user-guide/configure/)。
6 |
7 | 先来安装一些必要的 NPM 包:
8 |
9 | ```sh
10 | # StyleLint
11 | npm i -D stylelint
12 | # StyleLint 的 Prettier 插件,类似 eslint-plugin-prettier
13 | npm i -D stylelint-prettier
14 | # 禁用与 Prettier 冲突的 StyleLint 规则
15 | npm i -D stylelint-config-prettier
16 | # 基础规则(可选)
17 | npm i -D stylelint-config-standard
18 | ```
19 |
20 | 配置 `.stylelintrc.js`:
21 |
22 | ```js
23 | module.exports = {
24 | "plugins": ["stylelint-prettier"],
25 | "extends": [
26 | "stylelint-config-standard",
27 | "stylelint-config-prettier"
28 | ],
29 | "rules": {
30 | "prettier/prettier": true
31 | }
32 | }
33 | ```
34 |
35 | 经过刚刚 ESLint 的配置,相信你看这份配置应该没什么难度了:
36 |
37 | * 用 Prettier 插件格式化代码
38 | * 然后添加了 standard 以及 Prettier 的规则集,并禁用 StyleLint 一些和 Prettier 冲突的规则
39 | * 最后开启 Prettier 规则
40 |
41 | 造一个屎山 `./src/messyCss.css` 玩去吧。
42 |
43 | ## StyleLint x Less
44 |
45 | **不过,当要配置 less 或者 scss 还需要一点步骤。**
46 |
47 | ```sh
48 | # StyleLint 的 Less 插件
49 | npm i -D stylelint-less
50 | # StyleLint 的 Less 规则
51 | npm i -D stylelint-config-recommended-less
52 | # StyleLint 处理 customSyntax
53 | npm i -D postcss-less
54 | ```
55 |
56 | 修改配置:
57 |
58 | ```js
59 | module.exports = {
60 | "plugins": ["stylelint-prettier"],
61 | "extends": [
62 | "stylelint-config-standard",
63 | "stylelint-config-recommended-less",
64 | "stylelint-config-prettier"
65 | ],
66 | "customSyntax": "postcss-less",
67 | "rules": {
68 | "prettier/prettier": true
69 | }
70 | }
71 | ```
72 |
73 | 大功告成!再造个屎山 `./src/messyLess.less` 玩去吧~
74 |
75 | 只要你成功配置好了 StyleLint x Less,别的 CSS 预处理的 StyleLint 配置也是一样的,只需要吧上面的 `less` 替换掉就可以了。
76 |
77 | 官网也有介绍 [StyleLint x Scss](https://stylelint.io/ "StyleLint 官网") 的配置。
78 |
79 | ## 下一篇
80 |
81 | 这一篇非常的短,相信大家已经可以在 Linter 世界中遨游了。然而,事情并没有结束!
82 |
83 | 在日常开发中,我们不能保证所有人都会在写完代码后跑一次 Linter,有的人或许忘记开启 Linter 校验,有的人可能懒得修复,有的人可能根本就不知道有 Linter 这玩意!
84 |
85 | 所以,我们更希望可以在 `git commit` 或者 `git push` 前再做一次 Linter 操作,以此保证入库的代码都是经过修复的,一起来看看 [Husky x lint-staged](./husky_lint-staged) 吧。
86 |
--------------------------------------------------------------------------------
/docs/theory/history.md:
--------------------------------------------------------------------------------
1 | # 📖 Linter的故事
2 |
3 | 这一章跟大家聊聊这些 linter, prettier 的发展史和它们之间的关系,以及它们提供了哪些功能。
4 |
5 | ## 静态代码分析
6 |
7 | 早在 1978 年,Stephen C. Johnson 在 Debug 自己的 C 语言项目时,突然想到为什么不做一个工具来提示自己写的代码哪里有问题呢?
8 | 这个工具也被称为 Linter。
9 |
10 | Linter 本意指的是衣服上多出来的小球、绒毛和纤维等,如果你刚把晾晒好的衣服收下来就会发现这些小玩意。以前如果想把这些多出来的"残渣"去掉,
11 | 最简单的方法就是找一个单面胶粘一下再撕开,后来有的人发明了这个神器,一滚就能清除掉:
12 |
13 | 
14 |
15 | 这就是 Linter 的由来,不过区别是神器重点在 "清除",而 Linter 重点在 "上报错误"。
16 |
17 | Linter 想要提示错误,那首先就得阅读代码,这也是为什么 Linter 也被称为 **静态代码分析的工具**。阅读完之后,
18 | 再加上我们人为自定义好的一些规则,那么 Linter 就拥有了提示错误的能力了。
19 |
20 | 注意:这里的错误是广义上的错误,比如语法错误、潜在 Bug、代码风格等,这些上报的错误是需要人设定规则的,如果你不设定也是可以的,
21 | 比如你也可以做一个只检查代码风格的 Linter。所以说,Linter 并不等于 Compiler。
22 |
23 | ## JSLint
24 |
25 | 在 2002 年,Douglas Crockford 就为 JavaScript 写了第一个 Linter 工具:**JSLint**。
26 |
27 | 
28 |
29 | 你现在也可以在 https://www.jslint.com 这个网站上粘贴你的 JavaScript 代码来检查有没有问题。
30 |
31 | 
32 |
33 | JSLint 的优点就是 **开箱即用**,不需要配置太多的东西,相当于拎包入住。但优点也是缺点,就是 **规则太严格,完全不可扩展和自定义配置,连配置文件都没有。**
34 |
35 | 说白了,你要改 JSLint 是不可能的,你用不用吧。
36 |
37 | 
38 |
39 | ## JSHint
40 |
41 | 但是你这 JSLint 是生瓜蛋子啊,想改规则改不了,太不灵活了。所以 2010 的时候,Anton Kovalyov 跟其它人就 fork 了 JSLint 然后造成了 **JSHint**。
42 |
43 | 
44 |
45 | 这个工具与 JSLint 的思路正好相反,它的默认规则非常松散,自由度非常高了。但是也同样带来了问题:**你需要非常了解这些规则才能配出一个好用的规则表。因为规则太不严格,过于自由,所以单纯靠默认的规则跟没有配置 Linter 一样。**
46 |
47 | ## JSCS
48 |
49 | 前面的 JSLint 和 JSHint 主要功能都是检查代码质量问题的, **JSCS (JavaScript Coding Style) 则是一个代码风格检查器**。
50 |
51 | 它有超过 90 条规则,你也能自己创建规则,不过这些规则主要是和代码风格、代码格式化有关,它不会报任何和 JS 代码质量相关的错误。
52 |
53 | 
54 |
55 | 当然这东西现在也凉凉了。
56 |
57 | ## ESlint
58 |
59 | 2013 年,一个叫 **JSChecker** 的小项目被改名成我们如今非常熟悉的 **ESLint**。
60 |
61 | ### ES6
62 |
63 | 说到 ESLint 就要说一说 ES6 了。在 2014 年的时候,JavaScript 有一个非常大的更新:ES6,也叫做 ES2015。这个更新引入了很多的 API、新语法和新功能。详情可看阮一峰的 [《ES6标准入门》](https://es6.ruanyifeng.com/)。
64 |
65 | 
66 |
67 | ES6 上线了之后,JSHint 受不了直接投降了,因为它不支持这些 ES6 新语法。而 ESLint 正好异军突起,马上用 Esprima (一个高性能的 ECMAScript parser)支持所有 ES6 新语法,并对新语法做好了校验。
68 |
69 | 除了基础的 ES6 代码质量校验,ESLint 还支持代码风格的规则。开发者不仅可以自定义项目要用哪些规则,也能直接无脑使用社区上制定的规则(比如 eslint-config-airbnb)。
70 |
71 | 这一波操作也让 ESLint 成为现在 JavaScript 的一个标准的 Linter 了。然而,关于 Linter 的故事还没结束。
72 |
73 | ### 代码风格修复带来的麻烦
74 |
75 | 相信大家都知道 `eslint src --fix` 这行命令,我们都想当然地觉得 eslint 会帮我们修复一些 “错误”,可是大家有没有想过这里的 “错误” 究竟是什么呢?
76 |
77 | ESLint 把这里的 “错误” 分为两类:
78 | 1. 代码质量方面的错误/建议
79 | 2. 代码风格方面的错误/建议
80 |
81 | 代码质量非常容易理解,比如 [eqeqeq](https://eslint.org/docs/rules/eqeqeq) 这个把双等改三等的规则: `==` 改成 `===`,这样的规则可以有效地避免一些 Magic Code Bug。
82 |
83 | 
84 |
85 | 代码风格也是很容易理解,比如不喜欢加分号就加一条规则 [semi](https://eslint.org/docs/2.0.0/rules/semi) 规则:
86 |
87 | ```json
88 | {
89 | "semi": [2, "never"]
90 | }
91 | ```
92 |
93 | 一行 `eslint src --fix` 就能草飞整个项目的分号。但是,有一些规则是不能直接自动草飞的,比如 [max-len](https://eslint.org/docs/rules/max-len) 规则。
94 |
95 | 假如我们有下面非常长的一行代码,那么对下面的代码我们应该如何自动修复呢?
96 |
97 | ```js
98 | const foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficult": "to read" };
99 | ```
100 |
101 | 可以改成下面这样:
102 |
103 | ```js
104 | const foo = {
105 | bar: "This is a bar.",
106 | baz: { qux: "This is a qux" },
107 | difficult: "to read",
108 | };
109 | ```
110 |
111 | 也能改成下面这样:
112 |
113 | ```js
114 | const foo = {
115 | bar: "This is a bar.", baz: {
116 | qux: "This is a qux",
117 | }, difficult: "to read",
118 | };
119 | ```
120 |
121 | 它们都没超过 max-len,都能 pass。可是这两种写法到底哪个好看一点,ESLint 是无法界定的。
122 | 从这也可以看出 ESLint 的工作重点:**只管报错,遇到稍微要动点脑子的修复工作,比如怎么修复好看之类的就不管了,还是开发者自己决定吧。**。
123 |
124 | 如果只是代码质量方面的,比如尽量用 `const` 和 `let` 代替 `var`,大多数程序员都是愿意接受去改的。但在代码风格方面,就百花齐放,百家争鸣了。
125 | ESLint 对一些高级点的代码风格规则就不自动修复了,导致没有一个标准的基线可以参考,程序员时不时就 "要不要加分号" 问题大打出手。
126 |
127 | 这时,有的同学会说:不对呀,我在 IDE 里经常看到 `ESLint: Fix xxx` 来自动修复的呀,怎么能说 ESLint 撒手不管了呢:
128 |
129 | 
130 |
131 | 这里的 `ESLint: Fix xxx` 其实是用了 [Prettier](https://prettier.io/) 来做代码风格修复的。下面就来聊聊 [Prettier](https://prettier.io/) 这个工具。
132 |
133 | ## Prettier
134 |
135 | Prettier 是一个代码风格的修正工具。
136 |
137 | ### 如何诞生
138 |
139 | 代码风格是所有程序员都要遇到的问题,不管是团队协作还是个人练习。有的喜欢有分号,代码更安全;有的喜欢没分号,能少打一个字符;有的喜欢单引号,能少按一下 Shift;有的喜欢反引号,扩展更高;camelCase, PascalCase, snake_case 总是在团队里无法统一,就算统一了,有些队员心里也不服,因为代码风格太主观了,根本无法让谁信服谁,每个程序最喜欢看的代码还是自己的代码。
140 |
141 | 这就导致团队里总会出现因为一两个符号而争吵,从而引发内部矛盾,从而大打出手,进而各起山头,争风吃醋,团队解散,公司破产(咳咳)。
142 |
143 | 
144 |
145 | 前端程序员就会想:是否有一种非常标准且又好看的代码风格来停止这场代码风格的圣战呢?
146 |
147 | **Prettier 这时就出来了:我格式化后的代码是最好看的,谁同意,谁反对?**
148 |
149 | 
150 |
151 | **“我反对!凭什么你说最好看就是最好看?”**
152 |
153 | 
154 | **Pia!!!!**
155 |
156 | 
157 |
158 | 就凭你不会写论文!其实在很早之前已经有人开始研究哪种方式来格式化长文本是最好的(Prettier Printer),比如 Philip Wadler 在 [《A prettier printer》](https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf "A prettier printer") 这里给出了一些自动格式化换行的理论依据。
159 |
160 | > A good pretty printer
161 | must strike a balance between ease of use, flexibility of format, and optimality of
162 | output.
163 |
164 | Prettier 的作者 James 在这篇论文基础上再完善了一些代码风格规则,最终成为了 Prettier 格式化代码的最终方案。比如像下面的链式调用,Prettier 输出的就比原来论文描述的要好看一些:
165 |
166 | ```js
167 | // 原版 "A prettier printer" 的实现
168 | hello().then(() => {
169 | something()
170 | }).catch(console.error)
171 |
172 |
173 | // Prettier 的实现
174 | hello()
175 | .then(() => {
176 | something()
177 | })
178 | .catch(console.error)
179 | ```
180 |
181 | ### 如何工作
182 |
183 | 首先,Prettier 会把代码转换成 AST (Abstract Syntax Tree),这里用到的是一个叫 [Recast](https://github.com/benjamn/recast) 的库,而 Recast 实际上也用了 [Esprima](https://github.com/jquery/esprima) 来解析 ES6。
184 |
185 | 所以无论之前的代码怎么乱,怎么屎,Prettier 都抹掉之前的所有样式,抽成最本质的语法树。
186 |
187 | 然后再用 Prettier 的代码风格规则来输出格式化后的代码。
188 |
189 | 下面这图从左到右就是 Prettier 格式化代码的过程:`乱代码 -> AST -> 最终格式化后的结果`。
190 |
191 | 
192 |
193 | 从 Prettier 的工作原理也可以看出 Prettier 其实不仅仅可以为 JS 服务,还可以为其它语言/文件服务:
194 |
195 | 
196 |
197 |
198 | ### 设计理念
199 |
200 | Prettier 的官方文档里一直在强调自己是一个 Opinionated 的工具,这里想展开跟大家聊聊 Opinionated 。
201 |
202 | 其实不仅 Prettier,我们日常使用的一些库和框架都会标明自己是 opinionated 还是 unopinionated:
203 |
204 | 
205 |
206 | 
207 |
208 | 
209 |
210 | 按照框架/库的 opinionated 还是 unopinionated 思路来使用它们非常重要。
211 |
212 | Opinionated 的思路是 **你的一切我全包了,使用者就别自己发明设计模式和轮子,用我的就行,有锅我背。** 个人非常喜欢这样的思路,因为我实在不喜欢 “发明” 轮子和设计模式。认识我的人都知道我是一个 Jetbrains 的狂热粉,这正是因为 Jetbrains 家的 IDE 是按 Opinionated 思路来开发的,所有的功能 Jetbrains 全包圆了,各种工具的使用和联动一体性非常高,有种专为当前 IDE 服务的感觉。
213 |
214 | Unopinionated 的思路则是 **我就给你一堆零件,每个有优有劣,自己组装来玩了,相当于每人都是装机猿。** VSCode 就是类似这样的思路,平台非常开放,各种插件五花八门,你要 Vue,React,Ruby,Python 这些功能就得自己找插件来安装。对于喜欢个性化的玩家,自己的 VSCode 是可以玩出很多花样来,可是由于插件之间各自为政,要解决 Bug 和冲突就相对麻烦一些。
215 |
216 | Prettier 属于 Opinionated 哲学,这意味着它提供的代码风格已经是最优的,不希望使用者做太多自定义的内容,而应该相信 Prettier 已经服务到位了。
217 |
218 | ### Prettier + ESLint
219 |
220 | 我们可以把 ESLint 的 linter 规则分为两类:
221 |
222 | * 格式化规则:比如,max-len, no-mixed-spaces-and-tabs, keyword-spacing, comma-style... 等规则。在 ESLint 报错需要自动修复时,Prettier 可以将这些代码风格有问题的代码统一按最好看的风格来格式化。
223 | * 代码质量规则:比如,no-unused-vars, no-extra-bind, no-implicit-globals, prefer-promise-reject-errors... 等规则。在 ESLint 报错时,一般只能开发者自己手动处理。
224 |
225 | 刚刚说到 ESLint 对一些代码风格的自动修复束手无策,而 Prettier 正好是这方面的专家,所以我们可以让 Prettier 接管 ESLint 格式化代码的工作,自动修复违反格式化规则的代码,剩下的代码质量规则则还是交给 ESLint 自己上报错误。
226 |
227 | **总的来说:Prettier 用来格式化,Linter 用来检查 Bug!**
228 |
229 | ### 实践教程
230 |
231 | 至于要怎么结合 Prettier 和 ESLint,以及怎么去写 `.eslintrc.js` 里那些令人蒙逼的配置,打算放下一篇来讲(附示例哦),因文章太长了,可以关注一下【写代码的海怪】。
232 |
233 | ## TSLint
234 |
235 | 好了我们再说回 Linter。2012 微软公布了第一版的 TypeScript,随之而来的还有一个叫 TSLint 的 Linter。
236 |
237 | 在那段时间里,TSLint 是 TypeScript 的标准 Linter 工具,ESLint 则为 JavaScript 标准 Linter。它们各有自身特色:
238 | **ESLint 有 TSLint 所没有的一些语法特性支持,而 TSLint 可以对代码进行静态分析和类型检查。**
239 |
240 | 可是,一份代码还要两个 Linter 并行检查属实有点让人不爽。TSLint 也经常和 ESLint 的人探讨应该用哪个作为主力 Linter。TS 的社区也有很多声音希望优先满足 JSer 的需求,毕竟 TS 是 JS 的超集嘛,还是以 ESLint 为主。
241 |
242 | **最终,在 2019 年 TSLint 宣告不再维护,以后就是 ESLint 的天下了。**
243 |
244 | 
245 |
246 | TSLint 的静态分析和类型检查的功能则变成两个 NPM 包:[@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin),前者主要处理 `.ts` 文件,解析 TS 语法,后者则是提供 TS 的相关 lint 规则以及类型检查。在 Lint `.ts` 时,需要在 `.eslintrc.js` 里配置这两个包的信息。
247 |
248 | **在这场没有硝烟的 Linter 大战中,ESLint 最终吃鸡!**
249 |
250 | ## StyleLint
251 |
252 | 唯一没有被卷入这场大战中的就是 [StyleLint](https://stylelint.io/)。这是一个专为样式文件 `.css`, `.less`, `.scss` 做的 Linter。
253 |
254 | 虽然也有 lesslint, scss-lint 这些玩意,但是并没有击起太大的水花,StyleLint 可以说是直接一统江湖了。
255 |
256 | ## husky
257 |
258 | 如今已经来到 2022 年了,无论哪家的 IDE 还是编辑器都已经能够很好的支持 Linter 工具了。写代码时都能马上提示:
259 |
260 | 
261 |
262 | 除了直接快捷键修复,也能在保存的时候自动运行 `eslint --fix`。
263 |
264 | 可万一有些人不开 ESLint,或者忘记 ESLint 修复时,那些没有被 fix 过的代码也有可能会入库。所以我们希望在提交代码前能运行 `eslint --fix` 来自动修复有问题的代码。
265 |
266 | 
267 |
268 | [husky](https://github.com/typicode/husky) 就是用来解决这样问题的一个工具,它在提交的时候执行一些 bash 命令。
269 |
270 | 
271 |
272 | 比如,我们可以在每次提交时都 fix `/src` 下的所有代码。
273 |
274 | ```sh
275 | # pre-commit
276 | #!/bin/sh
277 | . "$(dirname "$0")/_/husky.sh"
278 |
279 | npx eslint src --fix
280 | ```
281 |
282 | ## lint-staged
283 |
284 | 如果每次都把整个 `/src` 都 fix 一遍感觉有点大可不必,不仅速度慢,还自动修复了很多没有改动过的文件,导致提交的文件变得更多。
285 |
286 | 更好的解决方法应该是只针对当前提交/改动过的文件进行 `eslint --fix`。
287 |
288 | 要实现这样的效果,Prettier 在官网的 [Pre-commit Hooks ](https://prettier.io/docs/en/precommit.html) 介绍了好几个工具。这里用 [lint-staged](https://github.com/okonet/lint-staged) 做例子,也是最多人熟悉的一个工具。
289 |
290 | 在刚刚的 `pre-commit.sh` 里改一下命令,每次提交前会执行 `lint-staged` 命令,而不是 `eslint --fix` 啦:
291 |
292 | ```sh
293 | #!/bin/sh
294 | . "$(dirname "$0")/_/husky.sh"
295 |
296 | npx lint-staged
297 | ```
298 |
299 | 之后在 `.lintstagedrc.js` 里可以指定对 **Git Commit** 上来的哪些文件执行哪些命令:
300 |
301 | ```js
302 | module.exports = {
303 | "*.{js,jsx,ts,tsx}": [
304 | "eslint --cache --fix",
305 | "bash -c 'tsc --noEmit'"
306 | ],
307 | "*.vue": [
308 | "eslint --cache --fix"
309 | ],
310 | "**/*.{css,less}": [
311 | "stylelint --cache --fix"
312 | ]
313 | }
314 | ```
315 |
316 | 配置好了之后在下次 commit 就可自动对提交的文件进行 lint fix 和 tsc 的检查了。
317 |
318 | **这部分实践会在下一篇 Linter 实践详细说明,这里大家懂我说的意思就好了~**
319 |
320 | ## 总结
321 |
322 | 好了一下子又写了那么多字,来稍微总结一下吧:
323 |
324 | 市面上那么多的 linter,只需要看 ESLint 就可以了。
325 |
326 | ESLint 主要负责处理两种规则:格式化和代码质量问题。格式化不是它的强项,需要用 Prettier 格式化大师来处理代码风格有问题的代码。
327 |
328 | Prettier 格式化后的代码,理论上是最好看的(你觉得不是就写一篇论文出来驳它跑马场 doge)。正因为 Prettier 只关注格式化,所以它也可以格式化其它的文件,如 `.yml`, `.json`, `.md` 等。
329 |
330 | TSLint 已经凉了,如果要 lint `.ts`,需要用到 [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser), [@typescript-eslint/eslint-plugin](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) 两个 NPM 包,这两可以看成 ESLint 的附属插件/补丁包。
331 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "linter-toturial",
3 | "version": "1.0.0",
4 | "description": "学习 prettier 和 linter",
5 | "main": "index.js",
6 | "scripts": {
7 | "prettier": "npx prettier --write .",
8 | "prepare": "husky install",
9 | "docs:dev": "vuepress dev docs",
10 | "docs:build": "vuepress build docs"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/haixiangyan/linter-toturial.git"
15 | },
16 | "keywords": [],
17 | "author": "",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/haixiangyan/linter-toturial/issues"
21 | },
22 | "homepage": "https://github.com/haixiangyan/linter-toturial#readme",
23 | "devDependencies": {
24 | "@babel/core": "^7.16.7",
25 | "@babel/eslint-parser": "^7.16.5",
26 | "@babel/preset-react": "^7.16.7",
27 | "@types/react": "^17.0.38",
28 | "@typescript-eslint/eslint-plugin": "^5.9.0",
29 | "@typescript-eslint/parser": "^5.9.0",
30 | "@vuepress/plugin-active-header-links": "^1.9.5",
31 | "@vuepress/plugin-back-to-top": "^1.9.5",
32 | "@vuepress/plugin-medium-zoom": "^1.9.5",
33 | "eslint": "^7.32.0",
34 | "eslint-config-prettier": "^8.3.0",
35 | "eslint-plugin-prettier": "^4.0.0",
36 | "eslint-plugin-react": "^7.28.0",
37 | "eslint-plugin-vue": "^8.2.0",
38 | "husky": "^7.0.4",
39 | "lint-staged": "^12.1.5",
40 | "postcss-less": "^5.0.0",
41 | "prettier": "^2.5.1",
42 | "stylelint": "^14.2.0",
43 | "stylelint-config-prettier": "^9.0.3",
44 | "stylelint-config-recommended-less": "^1.0.1",
45 | "stylelint-config-standard": "^24.0.0",
46 | "stylelint-less": "^1.0.1",
47 | "stylelint-prettier": "^2.0.0",
48 | "typescript": "^4.5.4",
49 | "vuepress": "^1.9.5"
50 | },
51 | "dependencies": {
52 | "react": "^17.0.2"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/ButtonVue.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
1
34 |
hello world man
35 |
36 |
37 |
38 |
50 |
51 |
56 |
--------------------------------------------------------------------------------
/src/button.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const a = 1;
4 | console.log(a, 1);
5 | console.log(a, 2);
6 |
7 | const Button = () => {
8 | return hello
;
9 | };
10 |
11 | export default Button;
12 |
--------------------------------------------------------------------------------
/src/happy.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Button = () => {
4 | return hello
;
5 | };
6 |
7 | export default Button;
8 |
--------------------------------------------------------------------------------
/src/messyCSS.css:
--------------------------------------------------------------------------------
1 | .container {
2 | background: red;
3 | }
4 |
5 | .container > div {
6 | color: blue;
7 | }
8 |
9 | /* .hello {} */
10 |
--------------------------------------------------------------------------------
/src/messyJS.js:
--------------------------------------------------------------------------------
1 | // import {hi, aa} from './messySubJS'
2 | // let x = 1
3 | //
4 | // // eslint error
5 | // for (let i = 0; i < 10; i--) {
6 | // console.log(i);
7 | // }
8 | //
9 | // // eslint error
10 | // const foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficult": "to readjalsdjflajsdflkjalsdfjlaksdjflkajsdflkajslkdfjalksjdflkasjdflkasj" }; // very long
11 | // console.log('foo', foo);
12 | //
13 | // if (foo) {
14 | // //...
15 | // }else if (x) {
16 | // //...
17 | // }else {
18 | // //...
19 | // }
20 | //
21 | // let y = {
22 | // name: 'Jack', age: 11 }
23 | //
24 | // console.log('result'
25 | // ,x,
26 | // y)
27 | //
28 | // console.log(hi, aa)
29 |
30 | import { hi, aa } from "./messySubJS";
31 | let x = 1;
32 |
33 | // // eslint error
34 | // for (let i = 0; i < 10; i--) {
35 | // console.log(i);
36 | // }
37 |
38 | // // eslint error
39 | // const foo = { "bar": "This is a bar.", "baz": { "qux": "This is a qux" }, "difficult": "to readjalsdjflajsdflkjalsdfjlaksdjflkajsdflkajslkdfjalksjdflkasjdflkasj" }; // very long
40 | // console.log('foo', foo);
41 |
42 | const foo = 1;
43 |
44 | if (foo) {
45 | //...
46 | } else if (x) {
47 | //...
48 | } else {
49 | //...
50 | }
51 |
52 | let y = {
53 | name: "Jack",
54 | age: 11,
55 | };
56 |
57 | console.log("result", x, y);
58 |
59 | console.log(hi, aa);
60 |
--------------------------------------------------------------------------------
/src/messyLESS.less:
--------------------------------------------------------------------------------
1 | .container {
2 | background: red;
3 | }
4 |
5 | //a { color: #y3 }
6 |
7 | .container > div {
8 | color: blue;
9 | }
10 |
--------------------------------------------------------------------------------
/src/messySubJS.js:
--------------------------------------------------------------------------------
1 | // export const hi = 'hi';
2 | // export const aa = {s: 1,
3 | // name: 2}
4 |
5 | export const hi = "hi";
6 | export const aa = { s: 1, name: 2 };
7 |
--------------------------------------------------------------------------------
/src/messyTS.ts:
--------------------------------------------------------------------------------
1 | // import {Human} from "./messyTypes";
2 | //
3 | // const x = 1
4 | //
5 | // const y = {
6 | // name: 'Jack', age: 11 }
7 | //
8 | // console.log(
9 | // 'z',x,y
10 | // )
11 | //
12 | // const z: any = 1
13 | // console.log('z',z)
14 | //
15 | // const human: Human = {
16 | // noise: 1,
17 | //
18 | //
19 | //
20 | // eye: 2,
21 | // }
22 | //
23 | // const hello: Hello = {
24 | // name: 'xx', age: 11
25 | // }
26 | //
27 | // console.log('x', human, hello)
28 |
29 | import { Human } from "./messyTypes";
30 |
31 | const x = 1;
32 |
33 | const y = {
34 | name: "Jack",
35 | age: 11,
36 | };
37 |
38 | // y.z = 1;
39 | // y.z = 2;
40 | // y.z = 2;
41 |
42 | console.log("z", x, y);
43 |
44 | const z: any = 1;
45 | console.log("z", z);
46 | console.log("z", z);
47 |
48 | const human: Human = {
49 | noise: 1,
50 | eye: 2,
51 | };
52 |
53 | console.log("hello", 1, 2);
54 | console.log("world", 1, 2);
55 |
56 | const za = 1;
57 |
58 | const hello: Hello = {
59 | name: "xx",
60 | age: 11,
61 | };
62 |
63 | console.log("x", human, hello);
64 |
--------------------------------------------------------------------------------
/src/messyTypes.ts:
--------------------------------------------------------------------------------
1 | // export interface Human {
2 | // eye:number
3 | // noise: number
4 | // }
5 |
6 | export interface Human {
7 | eye: number;
8 | noise: number;
9 | }
10 |
--------------------------------------------------------------------------------
/src/messyTypesInfo.d.ts:
--------------------------------------------------------------------------------
1 | // type Hello = {
2 | // name: string; age:number
3 | // }
4 |
5 | type Hello = {
6 | name: string;
7 | age: number;
8 | };
9 |
--------------------------------------------------------------------------------
/src/tsx.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from "react";
2 |
3 | interface Props {
4 | name: string;
5 | }
6 |
7 | const Button: FC = (props) => {
8 | console.log("porps", props);
9 | return hello
;
10 | };
11 |
12 | export default Button;
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | "jsx": "react", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "commonjs", /* Specify what module code is generated. */
28 | "rootDir": "./", /* Specify the root folder within your source files. */
29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | // "outDir": "./", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | },
101 | "include": ["src"],
102 | "exclude": ["node_modules"]
103 | }
104 |
--------------------------------------------------------------------------------