├── .markdownlintignore ├── pnpm-workspace.yaml ├── packages ├── eslint-config │ ├── .gitignore │ ├── __tests__ │ │ ├── fixtures │ │ │ ├── index.js │ │ │ ├── es5.js │ │ │ ├── ts-import-a.ts │ │ │ ├── ts-import-b.ts │ │ │ ├── tsconfig.json │ │ │ ├── react-display-name.js │ │ │ ├── vue.vue │ │ │ ├── ts-node.ts │ │ │ ├── ts.ts │ │ │ ├── node.js │ │ │ ├── ts-react.tsx │ │ │ ├── ts-vue.vue │ │ │ ├── react.jsx │ │ │ └── use-babel-eslint.jsx │ │ └── use-babel-eslint.test.js │ ├── .eslintrc.js │ ├── jsx-a11y.js │ ├── rules │ │ ├── base │ │ │ ├── strict.js │ │ │ ├── variables.js │ │ │ ├── possible-errors.js │ │ │ ├── es6.js │ │ │ └── best-practices.js │ │ ├── es5.js │ │ ├── node.js │ │ ├── jsx-a11y.js │ │ ├── vue.js │ │ └── imports.js │ ├── node.js │ ├── typescript │ │ ├── node.js │ │ ├── index.js │ │ ├── react.js │ │ └── vue.js │ ├── essential │ │ ├── typescript │ │ │ ├── index.js │ │ │ ├── react.js │ │ │ └── vue.js │ │ ├── vue.js │ │ ├── index.js │ │ ├── es5.js │ │ ├── rules │ │ │ ├── ts-blacklist.js │ │ │ ├── set-style-to-warn.js │ │ │ ├── blacklist.js │ │ │ └── es6-blacklist.js │ │ └── react.js │ ├── .eslintignore │ ├── react.js │ ├── es5.js │ ├── vue.js │ ├── .editorconfig │ ├── index.js │ └── package.json ├── encode-fe-lint │ ├── src │ │ ├── lints │ │ │ ├── prettier │ │ │ │ ├── index.ts │ │ │ │ └── doPrettier.ts │ │ │ ├── eslint │ │ │ │ ├── index.ts │ │ │ │ ├── doEslint.ts │ │ │ │ ├── getESLintConfigType.ts │ │ │ │ ├── formatESLintResults.ts │ │ │ │ └── getESLintConfig.ts │ │ │ ├── index.ts │ │ │ ├── stylelint │ │ │ │ ├── index.ts │ │ │ │ ├── getStylelintDocUrl.ts │ │ │ │ ├── doStylelint.ts │ │ │ │ ├── formatStylelintResults.ts │ │ │ │ └── getStylelintConfig.ts │ │ │ └── markdownlint │ │ │ │ ├── index.ts │ │ │ │ ├── getMarkdownlintConfig.ts │ │ │ │ ├── formatMarkdownlintResults.ts │ │ │ │ └── doMarkdownlint.ts │ │ ├── config │ │ │ ├── commitlint.config.js.ejs │ │ │ ├── _markdownlint.json.ejs │ │ │ ├── _stylelintrc.js.ejs │ │ │ ├── _eslintignore.ejs │ │ │ ├── _stylelintignore.ejs │ │ │ ├── _markdownlintignore.ejs │ │ │ ├── _vscode │ │ │ │ ├── extensions.json.ejs │ │ │ │ └── settings.json.ejs │ │ │ ├── _prettierrc.js.ejs │ │ │ ├── _eslintrc.js.ejs │ │ │ ├── encode-fe-lint.config.js.ejs │ │ │ └── _editorconfig.ejs │ │ ├── utils │ │ │ ├── npm-type.ts │ │ │ ├── log.ts │ │ │ ├── git.ts │ │ │ ├── generate-template.ts │ │ │ ├── constants.ts │ │ │ ├── print-report.ts │ │ │ └── conflict-resolve.ts │ │ ├── index.ts │ │ ├── actions │ │ │ ├── update.ts │ │ │ ├── scan.ts │ │ │ └── init.ts │ │ ├── types.ts │ │ └── cli.ts │ ├── .eslintignore │ ├── .gitignore │ ├── test │ │ ├── fixtures │ │ │ ├── autofix │ │ │ │ ├── semi-error.js │ │ │ │ └── semi-expected.js │ │ │ └── template │ │ │ │ └── init │ │ │ │ ├── _vscode │ │ │ │ └── settings.json │ │ │ │ └── package.json │ │ ├── index.test.js │ │ └── cli.test.js │ ├── .prettierrc.js │ ├── .eslintrc.js │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── eslint-plugin │ ├── configs │ │ └── recommended.js │ ├── index.js │ ├── rules │ │ ├── no-http-url.js │ │ ├── no-js-in-ts-project.js │ │ ├── no-secret-info.js │ │ └── no-broad-semantic-versioning.js │ ├── test │ │ └── rules │ │ │ ├── no-js-in-ts-project.test.js │ │ │ ├── no-broad-semantic-versioning.test.js │ │ │ ├── no-http-url.test.js │ │ │ └── no-secret-info.test.js │ ├── package.json │ └── README.md ├── stylelint-config │ ├── __tests__ │ │ ├── fixtures │ │ │ ├── sass-test.scss │ │ │ ├── less-test.less │ │ │ ├── index.css │ │ │ ├── essential.css │ │ │ └── css-module.scss │ │ └── rules-validate.test.js │ ├── README.md │ ├── package.json │ └── index.js ├── markdownlint-config │ ├── README.md │ ├── package.json │ └── index.json └── commitlint-config │ ├── package.json │ ├── index.js │ └── README.md ├── .stylelintrc ├── .markdownlint.json ├── commitlint.config.js ├── .husky └── commit-msg ├── docs ├── .vuepress │ ├── public │ │ └── img │ │ │ └── logo.png │ └── config.ts ├── npm │ ├── stylelint.md │ ├── markdownlint.md │ ├── commitlint.md │ └── eslint-plugin.md ├── index.md ├── engineering │ ├── changelog.md │ ├── doc.md │ └── git.md └── coding │ └── node.md ├── .vscode └── settings.json ├── .prettierrc.js ├── .gitignore ├── lerna.json ├── deploy.sh ├── CHANGELOG.md ├── .github └── workflows │ └── deploy.yml ├── LICENSE ├── package.json └── README.md /.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /packages/eslint-config/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | print-config.json 3 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./packages/stylelint-config/index.js" 3 | } -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./packages/markdownlint-config/index.json" 3 | } -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/prettier/index.ts: -------------------------------------------------------------------------------- 1 | export * from './doPrettier'; 2 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | test/ 4 | src/config/ 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['./packages/commitlint-config/index.js'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .DS_Store 4 | .idea 5 | .vscode 6 | coverage 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/commitlint.config.js.ejs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['encode'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/index.js: -------------------------------------------------------------------------------- 1 | let foo = [1,2] 2 | console.log(foo); 3 | eval('alert(1)'); 4 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/es5.js: -------------------------------------------------------------------------------- 1 | var hero = { 2 | firstName: 'Kevin', 3 | lastName: 'Flynn', 4 | }; 5 | -------------------------------------------------------------------------------- /docs/.vuepress/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/encode-studio-fe/fe-spec/HEAD/docs/.vuepress/public/img/logo.png -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/ts-import-a.ts: -------------------------------------------------------------------------------- 1 | import { b } from '@/ts-import-b'; 2 | 3 | export const a = b; 4 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/ts-import-b.ts: -------------------------------------------------------------------------------- 1 | import { a } from '@/ts-import-c'; 2 | 3 | export const b = a; 4 | -------------------------------------------------------------------------------- /packages/eslint-config/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | ].map(require.resolve), 5 | }; 6 | -------------------------------------------------------------------------------- /packages/eslint-config/jsx-a11y.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './rules/jsx-a11y', 4 | ].map(require.resolve), 5 | }; 6 | -------------------------------------------------------------------------------- /packages/eslint-config/rules/base/strict.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | // 不限制严格模式的使用 4 | strict: 'off', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["chenghuai", "commitlint", "stylelint", "vuepress"], 3 | "git.ignoreLimitWarning": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_markdownlint.json.ejs: -------------------------------------------------------------------------------- 1 | <%_ if (enableMarkdownlint) { _%> { "extends": "markdownlint-config-encode" } <%_ } _%> 2 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/test/fixtures/autofix/semi-error.js: -------------------------------------------------------------------------------- 1 | const hello = (name) => { 2 | return `hello, ${name}`; 3 | }; 4 | 5 | hello('world'); 6 | -------------------------------------------------------------------------------- /packages/eslint-config/node.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | './rules/node', 5 | ].map(require.resolve), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/test/fixtures/autofix/semi-expected.js: -------------------------------------------------------------------------------- 1 | const hello = (name) => { 2 | return `hello, ${name}`; 3 | }; 4 | 5 | hello('world'); 6 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/eslint/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getESLintConfig'; 2 | export * from './formatESLintResults'; 3 | export * from './doESLint'; 4 | -------------------------------------------------------------------------------- /packages/eslint-config/typescript/node.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | '../rules/node', 5 | ].map(require.resolve), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/index.ts: -------------------------------------------------------------------------------- 1 | export * from './eslint'; 2 | export * from './markdownlint'; 3 | export * from './stylelint'; 4 | export * from './prettier'; 5 | -------------------------------------------------------------------------------- /packages/eslint-config/typescript/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../index', 4 | '../rules/typescript', 5 | ].map(require.resolve), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/eslint-config/typescript/react.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../react', 4 | '../rules/typescript', 5 | ].map(require.resolve), 6 | }; 7 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_stylelintrc.js.ejs: -------------------------------------------------------------------------------- 1 | <%_ if (enableStylelint) { _%> 2 | module.exports = { 3 | extends: 'stylelint-config-encode', 4 | }; 5 | <%_ } _%> 6 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/stylelint/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getStylelintConfig'; 2 | export * from './formatStylelintResults'; 3 | export * from './doStylelint'; 4 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_eslintignore.ejs: -------------------------------------------------------------------------------- 1 | build/ 2 | coverage/ 3 | dist/ 4 | es/ 5 | lib/ 6 | node_modules/ 7 | **/*.min.js 8 | **/*-min.js 9 | **/*.bundle.js 10 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_stylelintignore.ejs: -------------------------------------------------------------------------------- 1 | <%_ if (enableStylelint) { _%> 2 | <%_ stylelintIgnores.forEach((txt) => { _%> 3 | <%= txt %> 4 | <% }) %> 5 | <%_ } _%> 6 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/react-display-name.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => { 4 | return ( 5 |

hello, world

6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | arrowParens: 'always', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/markdownlint/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getMarkdownlintConfig'; 2 | export * from './formatMarkdownlintResults'; 3 | export * from './doMarkdownlint'; 4 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_markdownlintignore.ejs: -------------------------------------------------------------------------------- 1 | <%_ if (enableMarkdownlint) { _%> 2 | <%_ markdownLintIgnores.forEach((txt) => { _%> 3 | <%= txt %> 4 | <% }) %> 5 | <%_ } _%> 6 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/test/fixtures/template/init/_vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": true, 3 | "editor.defaultFormatter": "233", 4 | "eslint.validate": [ 5 | "233" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.log 4 | 5 | .DS_Store 6 | .idea/ 7 | .temp/ 8 | 9 | dist/ 10 | build/ 11 | lib/ 12 | es/ 13 | coverage/ 14 | release/ 15 | 16 | node_modules/ 17 | 18 | .cache/ 19 | out/ -------------------------------------------------------------------------------- /packages/encode-fe-lint/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | arrowParens: 'always', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_vscode/extensions.json.ejs: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "stylelint.vscode-stylelint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/typescript/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../index', 4 | '../../rules/typescript', 5 | '../rules/ts-blacklist', 6 | ].map(require.resolve), 7 | }; 8 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/typescript/react.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../react', 4 | '../../rules/typescript', 5 | '../rules/ts-blacklist', 6 | ].map(require.resolve), 7 | }; 8 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_prettierrc.js.ejs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | arrowParens: 'always', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/vue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../vue', 4 | './rules/set-style-to-warn', 5 | './rules/blacklist', 6 | './rules/es6-blacklist', 7 | ].map(require.resolve), 8 | }; 9 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint-config-encode/typescript/node', 'prettier'], 3 | rules: { 4 | '@typescript-eslint/no-require-imports': 0, 5 | 'no-console': 0, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_eslintrc.js.ejs: -------------------------------------------------------------------------------- 1 | module.exports = { extends: [ '<%= eslintType === 'index' ? 'eslint-config-encode' : 2 | `eslint-config-encode/${eslintType}` %>', <%_ if (enablePrettier) { _%> 'prettier', <%_ } _%> ], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/eslint-plugin/configs/recommended.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['eslint-plugin-encode'], 3 | rules: { 4 | 'eslint-plugin-encode/no-http-url': 'warn', 5 | 'eslint-plugin-encode/no-secret-info': 'error', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/eslint-config/rules/es5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本文件用于覆盖掉个别 ES5 与 ES6 不同的规则 3 | */ 4 | 5 | module.exports = { 6 | rules: { 7 | // 逗号风格 - ES5 中不加最后一个逗号 8 | // @unessential 9 | 'comma-dangle': ['error', 'never'], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/eslint-config/.eslintignore: -------------------------------------------------------------------------------- 1 | # 忽略目录 2 | node_modules/ 3 | build/ 4 | dist/ 5 | zip/ 6 | demo/ 7 | coverage/ 8 | vendor/ 9 | lib/ 10 | sea-modules/ 11 | APP-META/ 12 | test/fixtures/ 13 | 14 | # 忽略文件 15 | **/*.min.js 16 | **/*-min.js 17 | **/*.bundle.js 18 | -------------------------------------------------------------------------------- /packages/eslint-config/react.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | './rules/react', 5 | ].map(require.resolve), 6 | parserOptions: { 7 | babelOptions: { 8 | presets: ['@babel/preset-react'], 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/encode-fe-lint.config.js.ejs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | enableESLint: <%= enableESLint %>, 3 | enableStylelint: <%= enableStylelint %>, 4 | enableMarkdownlint: <%= enableMarkdownlint %>, 5 | enablePrettier: <%= enablePrettier %>, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/typescript/vue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | '../../rules/vue', // vue 要置于最后,因为里面用了 vue-parser 5 | ].map(require.resolve), 6 | parserOptions: { 7 | parser: '@typescript-eslint/parser', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/eslint-config/es5.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './rules/base/best-practices', 4 | './rules/base/possible-errors', 5 | './rules/base/style', 6 | './rules/base/variables', 7 | './rules/es5', 8 | ].map(require.resolve), 9 | root: true, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/stylelint-config/__tests__/fixtures/sass-test.scss: -------------------------------------------------------------------------------- 1 | @mixin colorTag ($color1, $color2) { 2 | color: $color1; 3 | border-color: $color2; 4 | } 5 | 6 | //css注释 7 | .a { 8 | width: $default-width /2; 9 | 10 | @include colorTag(#abc,#bcd); 11 | @unkown colorTag(#abc); 12 | } 13 | -------------------------------------------------------------------------------- /packages/eslint-config/vue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | './rules/vue', 5 | ].map(require.resolve), 6 | parserOptions: { 7 | // https://github.com/mysticatea/vue-eslint-parser#parseroptionsparser 8 | parser: '@babel/eslint-parser', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * essential 级别出口文件仅将会必要的规则设置为 error 级别 3 | */ 4 | 5 | module.exports = { 6 | extends: [ 7 | '../index', 8 | './rules/set-style-to-warn', 9 | './rules/blacklist', 10 | './rules/es6-blacklist', 11 | ].map(require.resolve), 12 | }; 13 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_editorconfig.ejs: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | quote_type = single 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /packages/eslint-config/typescript/vue.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './index', 4 | '../rules/vue', 5 | ].map(require.resolve), 6 | parserOptions: { 7 | // https://github.com/mysticatea/vue-eslint-parser#parseroptionsparser 8 | parser: '@typescript-eslint/parser', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/es5.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../es5', 4 | './rules/set-style-to-warn', 5 | './rules/blacklist', 6 | ].map(require.resolve), 7 | rules: { 8 | // 逗号风格 - ES5 中不加最后一个逗号 9 | // @unessential 10 | 'comma-dangle': ['warn', 'never'], 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/utils/npm-type.ts: -------------------------------------------------------------------------------- 1 | import { sync as commandExistsSync } from 'command-exists'; 2 | 3 | /** 4 | * npm 类型 5 | */ 6 | const promise: Promise<'npm' | 'pnpm'> = new Promise((resolve) => { 7 | if (!commandExistsSync('pnpm')) return resolve('npm'); 8 | 9 | resolve('pnpm'); 10 | }); 11 | 12 | export default promise; 13 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.9", 3 | "npmClient": "pnpm", 4 | "useWorkspaces": true, 5 | "command": { 6 | "publish": { 7 | "npmClient": "npm", 8 | "message": "chore(release): publish %s", 9 | "registry": "https://registry.npmjs.org" 10 | }, 11 | "packages": [ 12 | "packages/*" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/test/fixtures/template/init/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encode-fe-lint-init-test", 3 | "version": "1.0.0", 4 | "description": "encode-fe-lint init test", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/vue.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /packages/eslint-config/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [makefile] 16 | indent_style = tab 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /packages/eslint-config/rules/node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本文件继承了 egg-config-egg 的 node 规则,规则由 eslint-plugin-node 提供 3 | * @link https://github.com/eggjs/eslint-config-egg/blob/master/lib/rules/node.js 4 | * @link https://github.com/mysticatea/eslint-plugin-node 5 | */ 6 | 7 | module.exports = { 8 | extends: [ 9 | 'eslint-config-egg/lib/rules/node', 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./lib", 4 | "target": "ES5", 5 | "module": "commonjs", 6 | "esModuleInterop": true, 7 | "removeComments": true, 8 | "resolveJsonModule": true, 9 | "declaration": true, 10 | "downlevelIteration": true, 11 | "types": ["node"] 12 | }, 13 | "include": ["src"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/rules/ts-blacklist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | // 使用 2 个空格缩进 4 | // @unessential 5 | '@typescript-eslint/indent': 'off', 6 | 7 | // 使用分号 8 | // @unessential 9 | '@typescript-eslint/semi': 'off', 10 | 11 | '@typescript-eslint/adjacent-overload-signatures': 'warn', 12 | 13 | '@typescript-eslint/no-parameter-properties': 'warn', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/ts-node.ts: -------------------------------------------------------------------------------- 1 | // node/prefer-promises/fs 2 | import fs from 'fs'; 3 | 4 | fs.readFile('../node.js', 'utf-8', (err, data) => { 5 | // no-console 6 | console.log(err, data); 7 | }); 8 | 9 | // no-var 10 | // @typescript-eslint/no-unused-vars 11 | var count = 1 + 1; 12 | 13 | class Test { 14 | func(): void { 15 | return 1; 16 | } 17 | } 18 | 19 | export default Test; 20 | // eol-last -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/ts.ts: -------------------------------------------------------------------------------- 1 | interface AppDeveloper { 2 | nickname: string, 3 | name: string, 4 | avatarUrl: string, 5 | id: number, 6 | } 7 | 8 | const foo: string = 1 9 | let bar = [1,2] 10 | const baz = {obj:2} 11 | 12 | function func(arg: string): string; 13 | function func(arg: number): number; 14 | function func(arg: string | number): string | number { 15 | if (typeof arg === 'string') return arg; 16 | return arg; 17 | } 18 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | 7 | push_addr=`git remote get-url --push origin` 8 | commit_info=`git describe --all --always --long` 9 | dist_path=docs/.vuepress/dist 10 | push_branch=gh-pages 11 | 12 | # 生成静态文件 13 | npm run docs:build 14 | 15 | # 进入生成的文件夹 16 | cd $dist_path 17 | 18 | git init 19 | git add -A 20 | git commit -m "deploy, $commit_info" 21 | git push -f $push_addr HEAD:$push_branch 22 | 23 | cd - 24 | rm -rf $dist_path 25 | -------------------------------------------------------------------------------- /packages/eslint-plugin/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const requireAll = require('require-all'); 3 | 4 | exports.rules = requireAll({ 5 | dirname: path.resolve(__dirname, 'rules'), 6 | }); 7 | 8 | exports.configs = requireAll({ 9 | dirname: path.resolve(__dirname, 'configs'), 10 | }); 11 | 12 | exports.processors = { 13 | '.json': { 14 | preprocess(text) { 15 | // As JS file 16 | return [`module.exports = ${text}`]; 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/stylelint-config/__tests__/fixtures/less-test.less: -------------------------------------------------------------------------------- 1 | @nice-bg: #abc; 2 | 3 | .normal { 4 | background: @nice-bg; 5 | 6 | .nest { 7 | font-size: 60px 8 | } 9 | } 10 | 11 | .my-hover-mixin() { 12 | &:hover { 13 | border: 1px solid red; 14 | } 15 | } 16 | .btn { 17 | .my-hover-mixin(); 18 | width: $default-width /2; 19 | :global(.ant-table) { 20 | font-size: 10px; 21 | } 22 | } 23 | .extProp:extend(.btn) { 24 | background-color:yellowgreen; 25 | } 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [](https://github.com/encode-studio-fe/fe-spec/compare/v0.0.6...v) (2023-06-11) 2 | 3 | 4 | ### Features 5 | 6 | * 更新 README.md ([73edb72](https://github.com/encode-studio-fe/fe-spec/commit/73edb7229db8e918d2045817a3e9a7e34feb3d3d)) 7 | * 更新 README.md ([9734505](https://github.com/encode-studio-fe/fe-spec/commit/9734505e1d9832d039c6850f6b58e1c007b23aa0)) 8 | * 添加脚手架 ([ae7e862](https://github.com/encode-studio-fe/fe-spec/commit/ae7e8628243033ae999dbcca085b8df9acdb93c2)) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/stylelint/getStylelintDocUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取 Stylelint 规则文档地址 3 | */ 4 | export function getStylelintRuleDocUrl(rule: string): string { 5 | // stylelint-scss 6 | const match = rule.match(/^@scss\/(\S+)$/); 7 | if (match) { 8 | return `https://github.com/kristerkari/stylelint-scss/tree/master/src/rules/${match[1]}`; 9 | } 10 | 11 | if (rule !== 'CssSyntaxError') return `https://stylelint.io/user-guide/rules/list/${rule}`; 12 | 13 | return ''; 14 | } 15 | -------------------------------------------------------------------------------- /packages/markdownlint-config/README.md: -------------------------------------------------------------------------------- 1 | # markdownlint-config-encode 2 | 3 | > 印客学院 文档 规范 4 | 5 | 支持配套的 [markdownlint 可共享配置](https://www.npmjs.com/package/markdownlint#optionsconfig)。 6 | 7 | ## 安装 8 | 9 | 需要先行安装 [markdownlint](https://www.npmjs.com/package/markdownlint): 10 | 11 | ```bash 12 | npm install markdownlint-config-encode markdownlint --save-dev 13 | ``` 14 | 15 | ## 使用 16 | 17 | 在 `.markdownlint.json` 中继承本包: 18 | 19 | ```json 20 | { 21 | "extends": "markdownlint-config-encode" 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/stylelint-config/README.md: -------------------------------------------------------------------------------- 1 | # markdownlint-config-encode 2 | 3 | 支持配套的 [stylelint 可共享配置](https://stylelint.io/user-guide/configure)。 4 | 5 | ## 安装 6 | 7 | 需要先行安装 [stylelint](https://www.npmjs.com/package/stylelint) 和 [stylelint-scss](https://www.npmjs.com/package/stylelint-scss): 8 | 9 | ```bash 10 | npm install stylelint-config-encode stylelint stylelint-scss --save-dev 11 | ``` 12 | 13 | ## 使用 14 | 15 | 在 `.stylelintrc` 中继承本包: 16 | 17 | ```json 18 | { 19 | "extends": "stylelint-config-encode" 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /packages/stylelint-config/__tests__/fixtures/index.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | color: #abc; 3 | } 4 | 5 | .selector{padding-left: 15px;} 6 | 7 | .selector1 { 8 | padding-left :15px; 9 | 10 | background-size: 0,0; 11 | } 12 | 13 | .selector2 {padding-left: 15px; 14 | } 15 | .selector3 { 16 | padding-left: 15px;} 17 | 18 | .selector4 { color: pink; top: 3px; } 19 | 20 | .selector5 { 21 | width: 0rem; 22 | 23 | color: #AAA; 24 | background: #aaaaaa; 25 | } 26 | 27 | #foo { 28 | color: #ccc; 29 | } 30 | 31 | /*comment */ 32 | 33 | @unkown colorTag(#abc); -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/node.js: -------------------------------------------------------------------------------- 1 | // 'no-var' 2 | var fs = require('fs'); 3 | 4 | // 'node/prefer-global/buffer' 5 | // 'quotes' 6 | const { Buffer } = require("buffer"); 7 | // 'node/no-new-require' 8 | // 'new-cap' 9 | const util = new require('util'); 10 | 11 | // 'no-console' 12 | console.log(util); 13 | // 'semi' 14 | // 'no-unused-vars' 15 | const b = Buffer.alloc(16) 16 | 17 | // 'node/prefer-promises/fs' 18 | fs.readFile('../node.ts', 'utf-8', (err, data) => { 19 | // 'no-console' 20 | console.log(err, data); 21 | }); 22 | 23 | exports = fs; 24 | // 'eol-last' -------------------------------------------------------------------------------- /packages/eslint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | './rules/base/best-practices', 4 | './rules/base/possible-errors', 5 | './rules/base/style', 6 | './rules/base/variables', 7 | './rules/base/es6', 8 | './rules/base/strict', 9 | './rules/imports', 10 | ].map(require.resolve), 11 | parser: '@babel/eslint-parser', 12 | parserOptions: { 13 | requireConfigFile: false, 14 | ecmaVersion: 2020, 15 | sourceType: 'module', 16 | ecmaFeatures: { 17 | globalReturn: false, 18 | impliedStrict: true, 19 | jsx: true, 20 | }, 21 | }, 22 | root: true, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/ts-react.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Props { 4 | name: string; 5 | enthusiasmLevel?: number; 6 | } 7 | 8 | function Hello({ name, enthusiasmLevel = 1 }: Props) { 9 | if (enthusiasmLevel <= 0) { 10 | throw new Error('You could be a little more enthusiastic. :D'); 11 | } 12 | 13 | const foo: number = 1 14 | 15 | return ( 16 |
17 |
18 | Hello {name + enthusiasmLevel} 19 |
20 | 21 | haha 22 | 23 |
24 | ); 25 | } 26 | 27 | export default Hello; 28 | -------------------------------------------------------------------------------- /packages/markdownlint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdownlint-config-encode", 3 | "version": "1.0.8", 4 | "main": "index.json", 5 | "description": "印客学院 文档规范", 6 | "keywords": [ 7 | "encode", 8 | "markdown", 9 | "lint" 10 | ], 11 | "author": "chenghuai", 12 | "homepage": "https://github.com/encode-studio-fe/fe-spec#readme", 13 | "license": "ISC", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/encode-studio-fe/fe-spec.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/encode-studio-fe/fe-spec/issues" 20 | }, 21 | "peerDependencies": { 22 | "markdownlint": "^0.28.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import { PKG_NAME, UNICODE } from './constants'; 3 | 4 | const { green, blue, yellow, red } = chalk; 5 | 6 | export default { 7 | success(text: string) { 8 | console.log(green(text)); 9 | }, 10 | info(text: string) { 11 | console.info(blue(text)); 12 | }, 13 | warn(text: string) { 14 | console.info(yellow(text)); 15 | }, 16 | error(text: string) { 17 | console.error(red(text)); 18 | }, 19 | result(text: string, pass: boolean) { 20 | console.info( 21 | blue(`[${PKG_NAME}] ${text}`), 22 | pass ? green(UNICODE.success) : red(UNICODE.failure), 23 | ); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/commitlint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commitlint-config-encode", 3 | "version": "1.0.8", 4 | "main": "index.js", 5 | "description": "印客学院 Git规范", 6 | "keywords": [ 7 | "encode", 8 | "commit", 9 | "lint" 10 | ], 11 | "author": "chenghuai", 12 | "homepage": "https://github.com/encode-studio-fe/fe-spec#readme", 13 | "license": "ISC", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/encode-studio-fe/fe-spec.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/encode-studio-fe/fe-spec/issues" 20 | }, 21 | "dependencies": { 22 | "conventional-changelog-conventionalcommits": "^4.5.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/eslint-plugin/rules/no-http-url.js: -------------------------------------------------------------------------------- 1 | const RULE_NAME = 'no-http-url'; 2 | 3 | module.exports = { 4 | name: RULE_NAME, 5 | meta: { 6 | type: 'suggestion', 7 | fixable: null, 8 | messages: { 9 | noHttpUrl: 'Recommended "{{url}}" switch to HTTPS', 10 | }, 11 | }, 12 | create(context) { 13 | return { 14 | Literal: function handleRequires(node) { 15 | if (node.value && typeof node.value === 'string' && node.value.indexOf('http:') === 0) { 16 | context.report({ 17 | node, 18 | messageId: 'noHttpUrl', 19 | data: { 20 | url: node.value, 21 | }, 22 | }); 23 | } 24 | }, 25 | }; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /docs/npm/stylelint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: stylelint-config-encode 3 | categories: 4 | - 工程规范 5 | tags: 6 | - 工程规范 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/fe-spec 10 | --- 11 | 12 | # stylelint-config-encode 13 | 14 | :::tip 15 | 印客学院 CSS 规范 16 | ::: 17 | 18 | 支持配套的 [stylelint 可共享配置](https://stylelint.io/user-guide/configure)。 19 | 20 | ## 安装 21 | 22 | 需要先行安装 [stylelint](https://www.npmjs.com/package/stylelint) 和 [stylelint-scss](https://www.npmjs.com/package/stylelint-scss): 23 | 24 | ```bash 25 | npm install stylelint-config-encode stylelint stylelint-scss --save-dev 26 | ``` 27 | 28 | ## 使用 29 | 30 | 在 `.stylelintrc` 中继承本包: 31 | 32 | ```json 33 | { 34 | "extends": "stylelint-config-encode" 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/commitlint-config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserPreset: 'conventional-changelog-conventionalcommits', 3 | rules: { 4 | 'body-leading-blank': [1, 'always'], 5 | 'body-max-line-length': [2, 'always', 100], 6 | 'footer-leading-blank': [1, 'always'], 7 | 'footer-max-line-length': [2, 'always', 100], 8 | 'header-max-length': [2, 'always', 100], 9 | 'scope-case': [2, 'always', 'lower-case'], 10 | 'subject-case': [0], 11 | 'subject-empty': [2, 'never'], 12 | 'subject-full-stop': [2, 'never', '.'], 13 | 'type-case': [2, 'always', 'lower-case'], 14 | 'type-empty': [2, 'never'], 15 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'test', 'refactor', 'chore', 'revert']], 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/eslint-plugin/test/rules/no-js-in-ts-project.test.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../rules/no-js-in-ts-project'); 2 | const { RuleTester } = require('eslint'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('no-js-in-ts-project', rule, { 7 | valid: [ 8 | { 9 | filename: 'index.ts', 10 | code: '', 11 | }, 12 | { 13 | filename: '.stylelintrc.js', 14 | code: '', 15 | }, 16 | { 17 | filename: 'home.ts', 18 | code: '', 19 | }, 20 | ], 21 | 22 | invalid: [ 23 | { 24 | filename: 'home.js', 25 | code: '', 26 | errors: [ 27 | { 28 | message: 'The "home.js" is not recommended in TS project', 29 | }, 30 | ], 31 | }, 32 | ], 33 | }); 34 | -------------------------------------------------------------------------------- /docs/npm/markdownlint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: markdownlint-config-encode 3 | categories: 4 | - 工程规范 5 | tags: 6 | - 工程规范 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/fe-spec 10 | --- 11 | 12 | # markdownlint-config-encode 13 | 14 | :::tip 15 | 印客学院 文档 规范 16 | ::: 17 | 18 | 支持配套的 [markdownlint 可共享配置](https://www.npmjs.com/package/markdownlint#optionsconfig)。 19 | 20 | ## 安装 21 | 需要先全局安装 [markdownlint-cli](https://www.npmjs.com/package/markdownlint-cli) 22 | ```bash 23 | npm install -g markdownlint-cli 24 | ``` 25 | 26 | 再安装 [markdownlint](https://www.npmjs.com/package/markdownlint): 27 | 28 | ```bash 29 | npm install markdownlint-config-encode markdownlint --save-dev 30 | ``` 31 | 32 | ## 使用 33 | 34 | 在 `.markdownlint.json` 中继承本包: 35 | 36 | ```json 37 | { 38 | "extends": "markdownlint-config-encode" 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/stylelint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylelint-config-encode", 3 | "version": "1.0.9", 4 | "description": "印客学院 CSS规范", 5 | "main": "index.js", 6 | "keywords": [ 7 | "encode", 8 | "style", 9 | "lint" 10 | ], 11 | "scripts": { 12 | "test": "jest" 13 | }, 14 | "author": "chenghuai", 15 | "homepage": "https://github.com/encode-studio-fe/fe-spec#readme", 16 | "license": "ISC", 17 | "peerDependencies": { 18 | "stylelint": ">=8.3.0", 19 | "stylelint-scss": ">=2.0.0" 20 | }, 21 | "devDependencies": { 22 | "jest": "^29.5.0", 23 | "stylelint": "^14.3.0", 24 | "stylelint-scss": "^4.1.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/encode-studio-fe/fe-spec.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/encode-studio-fe/fe-spec/issues" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/eslint-config/rules/jsx-a11y.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本文件的规则由 eslint-plugin-jsx-a11y 提供 3 | * @link https://github.com/evcohen/eslint-plugin-jsx-a11y 4 | */ 5 | 6 | module.exports = { 7 | plugins: ['jsx-a11y'], 8 | rules: { 9 | 'jsx-a11y/alt-text': 'warn', 10 | 'jsx-a11y/img-redundant-alt': 'warn', 11 | 'jsx-a11y/anchor-has-content': 'warn', 12 | 'jsx-a11y/aria-props': 'warn', 13 | 'jsx-a11y/aria-proptypes': 'warn', 14 | 'jsx-a11y/aria-unsupported-elements': 'warn', 15 | 'jsx-a11y/aria-role': ['warn', { ignoreNonDOM: true }], // ignoreNonDom 为 true 时不检查用户自定义元素 16 | 'jsx-a11y/role-has-required-aria-props': 'warn', 17 | 'jsx-a11y/role-supports-aria-props': 'warn', 18 | 'jsx-a11y/iframe-has-title': 'warn', 19 | 'jsx-a11y/no-access-key': 'warn', 20 | 'jsx-a11y/no-distracting-elements': 'warn', 21 | 'jsx-a11y/scope': 'warn', 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/rules/set-style-to-warn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将 error 级别的 style 规则降级为 warn 3 | */ 4 | 5 | // 将传入 config 中 error 级别规则都改为 warn 级别 6 | function setErrorRulesToWarn(configPath) { 7 | const config = require(configPath); 8 | const { rules } = config; 9 | 10 | for (const ruleName in rules) { 11 | if (Object.prototype.hasOwnProperty.call(rules, ruleName)) { 12 | const ruleValue = rules[ruleName]; 13 | if (Array.isArray(ruleValue)) { 14 | // 'array-bracket-spacing': [ 'error', 'never' ] 这种规则写法 15 | if (ruleValue[0] === 'error') { 16 | ruleValue[0] = 'warn'; 17 | } 18 | } else if (ruleValue === 'error') { 19 | // 'new-parens': 'error' 这种规则写法 20 | rules[ruleName] = 'warn'; 21 | } 22 | } 23 | } 24 | 25 | return { 26 | rules, 27 | }; 28 | } 29 | 30 | module.exports = setErrorRulesToWarn('../../rules/base/style.js'); 31 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/fixtures/ts-vue.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | 35 | 37 | -------------------------------------------------------------------------------- /packages/commitlint-config/README.md: -------------------------------------------------------------------------------- 1 | # `commitlint-config-encode` 2 | 3 | > 印客学院 Git 规范 4 | 5 | 支持配套的 [commitlint 配置](https://commitlint.js.org/#/concepts-shareable-config),用于对 `git commit message` 进行校验。 6 | 7 | ## 安装 8 | 9 | 使用时,需要安装 [@commitlint/cli](https://www.npmjs.com/package/@commitlint/cli): 10 | 11 | ```bash 12 | npm install commitlint-config-encode @commitlint/cli --save-dev 13 | ``` 14 | 15 | ## 使用 16 | 17 | 在 `commitlint.config.js` 中集成本包: 18 | 19 | ```javascript 20 | module.exports = { 21 | extends: ['encode'], 22 | }; 23 | ``` 24 | 25 | ## 设置 git hook 26 | 27 | 可通过 [husky](https://www.npmjs.com/package/husky) 设置在 `git commit` 时触发 `commitlint`。 28 | 29 | 首先安装 husky: 30 | 31 | ```bash 32 | npm install husky --save-dev 33 | ``` 34 | 35 | 然后执行添加`commit-msg`: 36 | 37 | ```bash 38 | npx husky add .husky/commit-msg 'npx commitlint --edit $1' 39 | ``` 40 | 41 | 更多信息可参考 [commitlint 文档](https://commitlint.js.org/#/guides-local-setup?id=install-husky)。 42 | -------------------------------------------------------------------------------- /packages/eslint-plugin/test/rules/no-broad-semantic-versioning.test.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../rules/no-broad-semantic-versioning'); 2 | const { RuleTester } = require('eslint'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('no-broad-semantic-versioning', rule, { 7 | valid: [ 8 | { 9 | filename: 'package.json', 10 | code: `module.exports = ${JSON.stringify({ 11 | devDependencies: { 'eslint-plugin-encode': '^0.0.5' }, 12 | })}`, 13 | }, 14 | { 15 | filename: 'package.js', 16 | code: 'var t = 1', 17 | }, 18 | ], 19 | 20 | invalid: [ 21 | { 22 | filename: 'package.json', 23 | code: `module.exports = ${JSON.stringify({ 24 | devDependencies: { 'eslint-plugin-encode': '*' }, 25 | })}`, 26 | errors: [ 27 | { 28 | message: 'The "eslint-plugin-encode" is not recommended to use "*"', 29 | }, 30 | ], 31 | }, 32 | ], 33 | }); 34 | -------------------------------------------------------------------------------- /packages/eslint-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-encode", 3 | "version": "1.0.8", 4 | "description": "印客教育自定义eslint插件", 5 | "scripts": { 6 | "test": "jest", 7 | "prepublishOnly": "npm run test" 8 | }, 9 | "files": [ 10 | "configs/", 11 | "rules/", 12 | "README.md" 13 | ], 14 | "keywords": [ 15 | "encode", 16 | "eslint", 17 | "eslint-plugin" 18 | ], 19 | "main": "src/index.js", 20 | "author": "chenghuai", 21 | "homepage": "https://github.com/encode-studio-fe/fe-spec#readme", 22 | "license": "ISC", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/encode-studio-fe/fe-spec.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/encode-studio-fe/fe-spec/issues" 29 | }, 30 | "dependencies": { 31 | "fs-extra": "^9.0.1", 32 | "require-all": "^3.0.0" 33 | }, 34 | "devDependencies": { 35 | "eslint": "^8.7.0", 36 | "jest": "^26.6.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/stylelint-config/__tests__/fixtures/essential.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @unknown {} 3 | 4 | .a { 5 | color: #fff1az; 6 | 7 | /**/ 8 | 9 | font-weight: 400; 10 | color: #fff; 11 | 12 | padding-left: 10px; 13 | padding: 20px; 14 | 15 | font-family: 'Times', Times, serif; 16 | 17 | font-family: Helvetica, Arial, Verdana, Tahoma; 18 | 19 | top: calc(1px +2px); 20 | 21 | background: linear-gradient(bottom, #fff, #000); 22 | } 23 | 24 | @keyframes important1 { 25 | from { 26 | margin-top: 50px; 27 | } 28 | to { 29 | margin-top: 100px !important; 30 | } 31 | } 32 | 33 | @media screen and (unknown) { 34 | .a { 35 | color: #fff; 36 | } 37 | } 38 | 39 | @import 'a.css'; 40 | @import 'a.css'; 41 | 42 | .a { 43 | background: #000;; 44 | colr: blue; 45 | } 46 | // css注释 47 | .b:unknown { 48 | background: #000; 49 | } 50 | .b::unknown { 51 | background: #000; 52 | } 53 | unknown { 54 | content: "first 55 | second"; 56 | width: 10pixels; 57 | background: #000 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout ️ 11 | uses: actions/checkout@v2.3.1 12 | 13 | - name: lock npm version 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 18.14.0 17 | 18 | - name: Install and Build 19 | run: | 20 | npm i -g pnpm 21 | pnpm run init 22 | pnpm run docs:build 23 | env: 24 | NODE_OPTIONS: '--max_old_space_size=4096 --openssl-legacy-provider' 25 | 26 | - name: Deploy 27 | uses: JamesIves/github-pages-deploy-action@4.1.3 28 | with: 29 | BRANCH: gh-pages 30 | FOLDER: docs/.vuepress/dist 31 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 32 | -------------------------------------------------------------------------------- /packages/eslint-plugin/test/rules/no-http-url.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../rules/no-http-url'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('no-http-url', rule, { 9 | valid: [ 10 | { 11 | code: "var test = 'https://chenghuai.com';", 12 | }, 13 | ], 14 | 15 | invalid: [ 16 | { 17 | code: "var test = 'http://chenghuai.com';", 18 | output: "var test = 'http://chenghuai.com';", 19 | errors: [ 20 | { 21 | message: 'Recommended "http://chenghuai.com" switch to HTTPS', 22 | }, 23 | ], 24 | }, 25 | { 26 | code: "", 27 | output: "", 28 | parserOptions: { 29 | ecmaFeatures: { 30 | jsx: true, 31 | }, 32 | }, 33 | errors: [ 34 | { 35 | message: 'Recommended "http://chenghuai.com" switch to HTTPS', 36 | }, 37 | ], 38 | }, 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/index.ts: -------------------------------------------------------------------------------- 1 | import ora from 'ora'; 2 | import scanAction from './actions/scan'; 3 | import initAction from './actions/init'; 4 | import { PKG_NAME } from './utils/constants'; 5 | import printReport from './utils/print-report'; 6 | import type { InitOptions, ScanOptions } from './types'; 7 | 8 | type IInitOptions = Omit; 9 | 10 | export const init = async (options: IInitOptions) => { 11 | return await initAction({ 12 | ...options, 13 | checkVersionUpdate: false, 14 | }); 15 | }; 16 | 17 | export const scan = async (options: ScanOptions) => { 18 | const checking = ora(); 19 | checking.start(`执行 ${PKG_NAME} 代码检查`); 20 | 21 | const report = await scanAction(options); 22 | const { results, errorCount, warningCount } = report; 23 | let type = 'succeed'; 24 | if (errorCount > 0) { 25 | type = 'fail'; 26 | } else if (warningCount > 0) { 27 | type = 'warn'; 28 | } 29 | 30 | checking[type](); 31 | if (results.length > 0) printReport(results, false); 32 | 33 | return report; 34 | }; 35 | -------------------------------------------------------------------------------- /packages/eslint-plugin/test/rules/no-secret-info.test.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../rules/no-secret-info'); 2 | const { RuleTester } = require('eslint'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('no-secret-info', rule, { 7 | valid: [ 8 | { 9 | code: 'var accessKeySecret = process.env.ACCESS_KEY_SECRET;', 10 | }, 11 | { 12 | code: 'var password = "";', 13 | }, 14 | { 15 | code: ` 16 | var client ={ 17 | accessKeyToken: process.env.ACCESS_KEY_SECRET 18 | }; 19 | `, 20 | }, 21 | ], 22 | 23 | invalid: [ 24 | { 25 | code: "var accessKeySecret = 'xxxx';", 26 | errors: [ 27 | { 28 | message: 'Detect that the "xxxx" might be a secret token, Please check!', 29 | }, 30 | ], 31 | }, 32 | { 33 | code: ` 34 | var client ={ 35 | accessKeyToken: 'xxxx' 36 | }; 37 | `, 38 | errors: [ 39 | { 40 | message: 'Detect that the "xxxx" might be a secret token, Please check!', 41 | }, 42 | ], 43 | }, 44 | ], 45 | }); 46 | -------------------------------------------------------------------------------- /packages/stylelint-config/__tests__/fixtures/css-module.scss: -------------------------------------------------------------------------------- 1 | .commits { 2 | padding: 0; 3 | 4 | :global(.ant-timeline-item-content) { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | color: #fff1aa; 9 | 10 | &>span { 11 | margin-right: 20px; 12 | white-space: nowrap; 13 | user-select: none; 14 | } 15 | } 16 | } 17 | 18 | :global(.tree-node-yellow) { 19 | :global(.ant-tree-node-content-wrapper) { 20 | color: var(--color-yellow) !important; 21 | } 22 | } 23 | 24 | :global(.ant-tree-treenode-switcher-close), :global(.tree-node-file) { 25 | display: flex; 26 | overflow: hidden; 27 | 28 | :global(.ant-tree-switcher) { 29 | flex-shrink: 0; 30 | } 31 | 32 | :global(.ant-tree-node-content-wrapper) { 33 | flex-shrink: 1; 34 | display: inline-flex !important; 35 | overflow: hidden; 36 | 37 | :global(.ant-tree-iconEle) { 38 | flex-shrink: 0; 39 | } 40 | 41 | :global(.ant-tree-title) { 42 | flex-shrink: 1; 43 | overflow: hidden; 44 | text-overflow: ellipsis; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/rules/blacklist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | // 统一在点号之前换行 4 | // @unessential 5 | 'dot-location': ['warn', 'property'], 6 | 7 | // 禁止使用 eval 8 | // @unessential 部分场景必须使用 eval 9 | 'no-eval': 'warn', 10 | 11 | // 禁止使用类 eval 的方法,如 setTimeout 传入字符串 12 | // @unessential 13 | 'no-implied-eval': 'warn', 14 | 15 | // 禁止使用 javascript:url,如 location.href = 'javascript:void(0)'; 16 | // @unessential 17 | 'no-script-url': 'warn', 18 | 19 | // 禁止变量与外层作用域已存在的变量同名 20 | // @unessential 21 | 'no-shadow': 'warn', 22 | 23 | // 禁止出现多个连续空格 24 | // @unessential 25 | 'no-multi-spaces': [ 26 | 'warn', 27 | { 28 | ignoreEOLComments: false, 29 | }, 30 | ], 31 | 32 | // 使用 2 个空格缩进 33 | // @unessential 34 | indent: 'off', 35 | 36 | // 使用分号 37 | // @unessential 38 | semi: 'off', 39 | 40 | // 分号必须写在行尾 41 | // @unessential 42 | 'semi-style': 'off', 43 | 44 | // 不要直接在对象上调用 Object.prototypes 上的方法 45 | // @unessential 46 | 'no-prototype-builtins': 'warn', 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /packages/eslint-plugin/README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-encode 2 | 3 | ## 安装 4 | 5 | 除了本包,你需要同时安装 [ESlint](https://eslint.org/) 6 | 7 | ```shell 8 | $ npm install eslint-plugin-encode eslint --save-dev 9 | ``` 10 | 11 | ## 使用 12 | 13 | ### 引入插件 14 | 15 | ```js 16 | // .eslintrc.js 17 | module.exports = { 18 | plugin: ['eslint-config-encode'], 19 | rules: { 20 | 'eslint-plugin-encode/no-secret-info': 'error', 21 | }, 22 | }; 23 | ``` 24 | 25 | ### 使用 presets 26 | 27 | ```js 28 | // .eslintrc.js 29 | module.exports = { 30 | extends: 'plugin:eslint-plugin-encode/recommended', 31 | }; 32 | ``` 33 | 34 | ## 支持的规则 35 | 36 | - [`no-broad-semantic-versioning`](https://encode-studio-fe.github.io/fe-spec/plugin/no-broad-semantic-versioning.html) 不要指定宽泛的版本范围 37 | - [`no-http-url`](https://encode-studio-fe.github.io/fe-spec/plugin/no-http-url.html) 使用 HTTPS 协议头的 URL,而不是 HTTP 38 | - [`no-js-in-ts-project`](https://encode-studio-fe.github.io/fe-spec/plugin/no-js-in-ts-project.html) 不要在 TS 项目中使用 JS 39 | - [`no-secret-info`](https://encode-studio-fe.github.io/fe-spec/plugin/no-secret-info.html) 不要在代码中直接设置 `password` `token` and `secret` 信息 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 印客学院 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 | -------------------------------------------------------------------------------- /docs/npm/commitlint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: commitlint-config-encode 3 | categories: 4 | - 工程规范 5 | tags: 6 | - 工程规范 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/fe-spec 10 | --- 11 | 12 | # commitlint-config-encode 13 | 14 | :::tip 15 | 印客学院 Git 规范 16 | ::: 17 | 18 | 支持配套的 [commitlint 配置](https://commitlint.js.org/#/concepts-shareable-config),用于对 `git commit message` 进行校验。 19 | 20 | ## 安装 21 | 22 | 使用时,需要安装 [@commitlint/cli](https://www.npmjs.com/package/@commitlint/cli): 23 | 24 | ```bash 25 | npm install commitlint-config-encode @commitlint/cli --save-dev 26 | ``` 27 | 28 | ## 使用 29 | 30 | 在 `commitlint.config.js` 中集成本包: 31 | 32 | ```javascript 33 | module.exports = { 34 | extends: ['encode'], 35 | }; 36 | ``` 37 | 38 | ## 设置 git hook 39 | 40 | 可通过 [husky](https://www.npmjs.com/package/husky) 设置在 `git commit` 时触发 `commitlint`。 41 | 42 | 首先安装 husky: 43 | 44 | ```bash 45 | npm install husky --save-dev 46 | ``` 47 | 48 | 然后执行添加`commit-msg`: 49 | 50 | ```bash 51 | npx husky add .husky/commit-msg 'npx commitlint --edit $1' 52 | ``` 53 | 54 | 更多信息可参考 [commitlint 文档](https://commitlint.js.org/#/guides-local-setup?id=install-husky)。 55 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/markdownlint/getMarkdownlintConfig.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import glob from 'glob'; 3 | import markdownLint from 'markdownlint'; 4 | import markdownLintConfig from 'markdownlint-config-encode'; 5 | import type { ScanOptions, PKG, Config } from '../../types'; 6 | 7 | type LintOptions = markdownLint.Options & { fix?: boolean }; 8 | 9 | /** 10 | * 获取 Markdownlint 配置 11 | */ 12 | export function getMarkdownlintConfig(opts: ScanOptions, pkg: PKG, config: Config): LintOptions { 13 | const { cwd } = opts; 14 | const lintConfig: LintOptions = { 15 | fix: Boolean(opts.fix), 16 | resultVersion: 3, 17 | }; 18 | 19 | if (config.markdownlintOptions) { 20 | // 若用户传入了 markdownlintOptions,则用用户的 21 | Object.assign(lintConfig, config.markdownlintOptions); 22 | } else { 23 | const lintConfigFiles = glob.sync('.markdownlint(.@(yaml|yml|json))', { cwd }); 24 | if (lintConfigFiles.length === 0) { 25 | lintConfig.config = markdownLintConfig; 26 | } else { 27 | lintConfig.config = markdownLint.readConfigSync(path.resolve(cwd, lintConfigFiles[0])); 28 | } 29 | } 30 | 31 | return lintConfig; 32 | } 33 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/eslint/doEslint.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import fg from 'fast-glob'; 3 | import { extname, join } from 'path'; 4 | import { Config, PKG, ScanOptions } from '../../types'; 5 | import { ESLINT_FILE_EXT, ESLINT_IGNORE_PATTERN } from '../../utils/constants'; 6 | import { formatESLintResults } from './formatESLintResults'; 7 | import { getESLintConfig } from './getESLintConfig'; 8 | 9 | export interface DoESLintOptions extends ScanOptions { 10 | pkg: PKG; 11 | config?: Config; 12 | } 13 | 14 | export async function doESLint(options: DoESLintOptions) { 15 | let files: string[]; 16 | if (options.files) { 17 | files = options.files.filter((name) => ESLINT_FILE_EXT.includes(extname(name))); 18 | } else { 19 | files = await fg(`**/*.{${ESLINT_FILE_EXT.map((t) => t.replace(/^\./, '')).join(',')}}`, { 20 | cwd: options.cwd, 21 | ignore: ESLINT_IGNORE_PATTERN, 22 | }); 23 | } 24 | 25 | const eslint = new ESLint(getESLintConfig(options, options.pkg, options.config)); 26 | const reports = await eslint.lintFiles(files); 27 | if (options.fix) { 28 | await ESLint.outputFixes(reports); 29 | } 30 | 31 | return formatESLintResults(reports, options.quiet, eslint); 32 | } 33 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/stylelint/doStylelint.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob'; 2 | import { extname, join } from 'path'; 3 | import stylelint from 'stylelint'; 4 | import { PKG, ScanOptions } from '../../types'; 5 | import { STYLELINT_FILE_EXT, STYLELINT_IGNORE_PATTERN } from '../../utils/constants'; 6 | import { formatStylelintResults } from './formatStylelintResults'; 7 | import { getStylelintConfig } from './getStylelintConfig'; 8 | 9 | export interface DoStylelintOptions extends ScanOptions { 10 | pkg: PKG; 11 | } 12 | 13 | export async function doStylelint(options: DoStylelintOptions) { 14 | let files: string[]; 15 | if (options.files) { 16 | files = options.files.filter((name) => STYLELINT_FILE_EXT.includes(extname(name))); 17 | } else { 18 | const pattern = join( 19 | options.include, 20 | `**/*.{${STYLELINT_FILE_EXT.map((t) => t.replace(/^\./, '')).join(',')}}`, 21 | ); 22 | files = await fg(pattern, { 23 | cwd: options.cwd, 24 | ignore: STYLELINT_IGNORE_PATTERN, 25 | }); 26 | } 27 | const data = await stylelint.lint({ 28 | ...getStylelintConfig(options, options.pkg, options.config), 29 | files, 30 | }); 31 | return formatStylelintResults(data.results, options.quiet); 32 | } 33 | -------------------------------------------------------------------------------- /packages/eslint-config/rules/base/variables.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 预设的环境,使用这些环境中的全局变量不会被 no-undef 报错 3 | // @link https://eslint.org/docs/user-guide/configuring#specifying-environments 4 | env: { 5 | browser: true, 6 | es6: true, 7 | jasmine: true, 8 | jest: true, 9 | jquery: true, 10 | mocha: true, 11 | node: true, 12 | }, 13 | rules: { 14 | // 强制或禁止在变量声明时进行赋值 15 | 'init-declarations': 'off', 16 | 17 | // 禁止 delete 变量 18 | 'no-delete-var': 'error', 19 | 20 | // 禁止标签与变量同名 21 | 'no-label-var': 'error', 22 | 23 | // 禁用使用特定的全局变量 24 | 'no-restricted-globals': 'off', 25 | 26 | // 禁止变量与外层作用域已存在的变量同名 27 | // @unessential 28 | 'no-shadow': 'error', 29 | 30 | // 禁止使用保留字命名变量 31 | 'no-shadow-restricted-names': 'error', 32 | 33 | // 禁止使用未声明的变量 34 | 'no-undef': 'error', 35 | 36 | // 不要将变量初始化成 undefined 37 | 'no-undef-init': 'error', 38 | 39 | // 禁止使用 undefined 40 | 'no-undefined': 'off', 41 | 42 | // 声明的变量必须被使用 43 | 'no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }], 44 | 45 | // 不要在声明前就使用变量 46 | 'no-use-before-define': ['error', { functions: false, classes: false, variables: false }], 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/prettier/doPrettier.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob'; 2 | import { readFile, writeFile } from 'fs-extra'; 3 | import { extname, join } from 'path'; 4 | import prettier from 'prettier'; 5 | import { ScanOptions } from '../../types'; 6 | import { PRETTIER_FILE_EXT, PRETTIER_IGNORE_PATTERN } from '../../utils/constants'; 7 | 8 | export interface DoPrettierOptions extends ScanOptions {} 9 | 10 | export async function doPrettier(options: DoPrettierOptions) { 11 | let files: string[] = []; 12 | if (options.files) { 13 | files = options.files.filter((name) => PRETTIER_FILE_EXT.includes(extname(name))); 14 | } else { 15 | const pattern = join( 16 | options.include, 17 | `**/*.{${PRETTIER_FILE_EXT.map((t) => t.replace(/^\./, '')).join(',')}}`, 18 | ); 19 | files = await fg(pattern, { 20 | cwd: options.cwd, 21 | ignore: PRETTIER_IGNORE_PATTERN, 22 | }); 23 | } 24 | await Promise.all(files.map(formatFile)); 25 | } 26 | 27 | async function formatFile(filepath: string) { 28 | const text = await readFile(filepath, 'utf8'); 29 | const options = await prettier.resolveConfig(filepath); 30 | const formatted = prettier.format(text, { ...options, filepath }); 31 | await writeFile(filepath, formatted, 'utf8'); 32 | } 33 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/test/index.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | const encodeFeLint = require('../lib/index'); 4 | 5 | const { init } = encodeFeLint; 6 | 7 | describe('init', () => { 8 | const templatePath = path.resolve(__dirname, './fixtures/template/init'); 9 | const outputPath = path.resolve(__dirname, './fixtures/template/temp'); 10 | 11 | beforeEach(() => { 12 | fs.copySync(templatePath, outputPath); 13 | fs.renameSync(`${outputPath}/_vscode`, `${outputPath}/.vscode`); 14 | }); 15 | 16 | test('node api init should work as expected', async () => { 17 | await init({ 18 | cwd: outputPath, 19 | checkVersionUpdate: false, 20 | eslintType: 'index', 21 | enableStylelint: true, 22 | enableMarkdownlint: true, 23 | enablePrettier: true, 24 | }); 25 | 26 | const pkg = require(`${outputPath}/package.json`); 27 | const settings = require(`${outputPath}/.vscode/settings.json`); 28 | 29 | expect(settings['editor.defaultFormatter']).toBe('esbenp.prettier-vscode'); 30 | expect(settings['eslint.validate'].includes('233')).toBeTruthy(); 31 | expect(settings.test).toBeTruthy(); 32 | }); 33 | 34 | afterEach(() => { 35 | fs.removeSync(outputPath); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/stylelint/formatStylelintResults.ts: -------------------------------------------------------------------------------- 1 | import { LintResult } from 'stylelint'; 2 | import type { ScanResult } from '../../types'; 3 | import { getStylelintRuleDocUrl } from './getStylelintDocUrl'; 4 | 5 | /** 6 | * 格式化 Stylelint 输出结果 7 | */ 8 | export function formatStylelintResults(results: LintResult[], quiet: boolean): ScanResult[] { 9 | return results.map(({ source, warnings }) => { 10 | let errorCount = 0; 11 | let warningCount = 0; 12 | const messages = warnings 13 | .filter((item) => !quiet || item.severity === 'error') 14 | .map((item) => { 15 | const { line = 0, column = 0, rule, severity, text } = item; 16 | if (severity === 'error') { 17 | errorCount++; 18 | } else { 19 | warningCount++; 20 | } 21 | return { 22 | line, 23 | column, 24 | rule, 25 | url: getStylelintRuleDocUrl(rule), 26 | message: text.replace(/([^ ])\.$/u, '$1').replace(new RegExp(`\\(${rule}\\)`), ''), 27 | errored: severity === 'error', 28 | }; 29 | }); 30 | 31 | return { 32 | filePath: source, 33 | messages, 34 | errorCount, 35 | warningCount, 36 | fixableErrorCount: 0, 37 | fixableWarningCount: 0, 38 | }; 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/eslint/getESLintConfigType.ts: -------------------------------------------------------------------------------- 1 | import glob from 'glob'; 2 | import type { PKG } from '../../types'; 3 | 4 | /** 5 | * 获取 ESLint 配置类型 6 | * @param cwd 7 | * @param pkg 8 | * @returns eslint-config-encode/index 9 | * @returns eslint-config-encode/react 10 | * @returns eslint-config-encode/typescript/index 11 | * @returns eslint-config-encode/typescript/react 12 | */ 13 | export function getESLintConfigType(cwd: string, pkg: PKG): string { 14 | const tsFiles = glob.sync('./!(node_modules)/**/*.@(ts|tsx)', { cwd }); 15 | const reactFiles = glob.sync('./!(node_modules)/**/*.@(jsx|tsx)', { cwd }); 16 | const vueFiles = glob.sync('./!(node_modules)/**/*.vue', { cwd }); 17 | const dependencies = Object.keys(pkg.dependencies || {}); 18 | const language = tsFiles.length > 0 ? 'typescript' : ''; 19 | let dsl = ''; 20 | 21 | // dsl判断 22 | if (reactFiles.length > 0 || dependencies.some((name) => /^react(-|$)/.test(name))) { 23 | dsl = 'react'; 24 | } else if (vueFiles.length > 0 || dependencies.some((name) => /^vue(-|$)/.test(name))) { 25 | dsl = 'vue'; 26 | } else if (dependencies.some((name) => /^rax(-|$)/.test(name))) { 27 | dsl = 'rax'; 28 | } 29 | 30 | return ( 31 | 'eslint-config-encode/' + `${language}/${dsl}`.replace(/\/$/, '/index').replace(/^\//, '') 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/utils/git.ts: -------------------------------------------------------------------------------- 1 | import execa from 'execa'; 2 | 3 | /** 4 | * 获取此次 commit 修改的文件列表 5 | * @param options 6 | */ 7 | export const getCommitFiles = async (options: execa.Options = {}): Promise => { 8 | try { 9 | const { stdout } = await execa( 10 | 'git', 11 | [ 12 | 'diff', 13 | '--staged', // 比较 暂缓区 与 last commit 的差别 14 | '--diff-filter=ACMR', // 只显示 added、copied、modified、renamed 15 | '--name-only', // 只显示更改文件的名称 16 | '--ignore-submodules', 17 | ], 18 | { 19 | ...options, 20 | all: true, 21 | cwd: options.cwd || process.cwd(), 22 | }, 23 | ); 24 | 25 | return stdout ? stdout.split(/\s/).filter(Boolean) : []; 26 | } catch (e) { 27 | return []; 28 | } 29 | }; 30 | 31 | /** 32 | * 获取未 add 的修改文件数量 33 | * @param options 34 | */ 35 | export const getAmendFiles = async (options: execa.Options = {}): Promise => { 36 | try { 37 | const { stdout } = await execa( 38 | 'git', 39 | [ 40 | 'diff', // 比较 工作区 与 暂缓区的差别 41 | '--name-only', 42 | ], 43 | { 44 | ...options, 45 | all: true, 46 | cwd: options.cwd || process.cwd(), 47 | }, 48 | ); 49 | 50 | return stdout; 51 | } catch (e) { 52 | return ''; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/markdownlint/formatMarkdownlintResults.ts: -------------------------------------------------------------------------------- 1 | import markdownlint from 'markdownlint'; 2 | import type { ScanResult } from '../../types'; 3 | 4 | /** 5 | * 格式化 markdownlint 输出结果 6 | */ 7 | export function formatMarkdownlintResults( 8 | results: markdownlint.LintResults, 9 | quiet: boolean, 10 | ): ScanResult[] { 11 | const parsedResults = []; 12 | 13 | for (const file in results) { 14 | if (!Object.prototype.hasOwnProperty.call(results, file) || quiet) continue; 15 | 16 | let warningCount = 0; 17 | let fixableWarningCount = 0; 18 | const messages = results[file].map( 19 | ({ lineNumber, ruleNames, ruleDescription, ruleInformation, errorRange, fixInfo }) => { 20 | if (fixInfo) fixableWarningCount++; 21 | warningCount++; 22 | 23 | return { 24 | line: lineNumber, 25 | column: Array.isArray(errorRange) ? errorRange[0] : 1, 26 | rule: ruleNames[0], 27 | url: ruleInformation, 28 | message: ruleDescription, 29 | errored: false, 30 | }; 31 | }, 32 | ); 33 | 34 | parsedResults.push({ 35 | filePath: file, 36 | messages, 37 | errorCount: 0, 38 | warningCount, 39 | fixableErrorCount: 0, 40 | fixableWarningCount, 41 | }); 42 | } 43 | 44 | return parsedResults; 45 | } 46 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/stylelint/getStylelintConfig.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import glob from 'glob'; 3 | import path from 'path'; 4 | import { LinterOptions } from 'stylelint'; 5 | import type { Config, PKG, ScanOptions } from '../../types'; 6 | import { STYLELINT_IGNORE_PATTERN } from '../../utils/constants'; 7 | 8 | /** 9 | * 获取 Stylelint 配置 10 | */ 11 | export function getStylelintConfig(opts: ScanOptions, pkg: PKG, config: Config): LinterOptions { 12 | const { cwd, fix } = opts; 13 | if (config.enableStylelint === false) return {} as any; 14 | 15 | const lintConfig: any = { 16 | fix: Boolean(fix), 17 | allowEmptyInput: true, 18 | }; 19 | 20 | if (config.stylelintOptions) { 21 | // 若用户传入了 stylelintOptions,则用用户的 22 | Object.assign(lintConfig, config.stylelintOptions); 23 | } else { 24 | // 根据扫描目录下有无lintrc文件,若无则使用默认的 lint 配置 25 | const lintConfigFiles = glob.sync('.stylelintrc?(.@(js|yaml|yml|json))', { cwd }); 26 | if (lintConfigFiles.length === 0 && !pkg.stylelint) { 27 | lintConfig.config = { 28 | extends: 'stylelint-config-encode', 29 | }; 30 | } 31 | 32 | // 根据扫描目录下有无lintignore文件,若无则使用默认的 ignore 配置 33 | const ignoreFilePath = path.resolve(cwd, '.stylelintignore'); 34 | if (!fs.existsSync(ignoreFilePath)) { 35 | lintConfig.ignorePattern = STYLELINT_IGNORE_PATTERN; 36 | } 37 | } 38 | 39 | return lintConfig; 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encode-fe-spec", 3 | "private": true, 4 | "description": "印客学院--前端编码规范工程化", 5 | "scripts": { 6 | "preinstall": "npx only-allow pnpm", 7 | "prepare": "husky install ", 8 | "init": "pnpm install --no-frozen-lockfile", 9 | "clean": "lerna clean && rm -rf node_modules", 10 | "test": "lerna run test", 11 | "docs:dev": "vuepress dev docs", 12 | "docs:build": "vuepress build docs", 13 | "deploy": "bash deploy.sh", 14 | "publish": "lerna publish", 15 | "lint": "markdownlint README.md", 16 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" 17 | }, 18 | "keywords": [ 19 | "encode", 20 | "fe spec" 21 | ], 22 | "author": "chenghuai", 23 | "license": "ISC", 24 | "devDependencies": { 25 | "@commitlint/cli": "^17.6.1", 26 | "husky": "^8.0.3", 27 | "lerna": "^6.6.2", 28 | "markdownlint": "^0.28.1", 29 | "pnpm": "^8.6.0", 30 | "rimraf": "^3.0.2", 31 | "ts-node": "^10.9.1", 32 | "tslib": "^2.5.2", 33 | "typedoc": "^0.24.7", 34 | "typescript": "^5.0.4", 35 | "vue-template-compiler": "^2.7.14", 36 | "vuepress": "^1.9.9", 37 | "vuepress-plugin-one-click-copy": "^1.0.2", 38 | "vuepress-plugin-zooming": "^1.1.7" 39 | }, 40 | "dependencies": { 41 | "conventional-changelog-conventionalcommits": "^4.5.0" 42 | }, 43 | "publishConfig": { 44 | "access": "public", 45 | "registry": "https://registry.npmjs.org/" 46 | } 47 | } -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/eslint/formatESLintResults.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import type { ScanResult } from '../../types'; 3 | 4 | /** 5 | * 格式化 ESLint 输出结果 6 | */ 7 | export function formatESLintResults(results: ESLint.LintResult[], quiet: boolean, eslint: ESLint): ScanResult[] { 8 | const rulesMeta = eslint.getRulesMetaForResults(results); 9 | 10 | return results 11 | .filter(({ warningCount, errorCount }) => errorCount || warningCount) 12 | .map( 13 | ({ 14 | filePath, 15 | messages, 16 | errorCount, 17 | warningCount, 18 | fixableErrorCount, 19 | fixableWarningCount, 20 | }) => ({ 21 | filePath, 22 | errorCount, 23 | warningCount: quiet ? 0 : warningCount, 24 | fixableErrorCount, 25 | fixableWarningCount: quiet ? 0 : fixableWarningCount, 26 | messages: messages 27 | .map(({ line = 0, column = 0, ruleId, message, fatal, severity }) => { 28 | return { 29 | line, 30 | column, 31 | rule: ruleId, 32 | url: rulesMeta[ruleId]?.docs?.url || '', 33 | message: message.replace(/([^ ])\.$/u, '$1'), 34 | errored: fatal || severity === 2, 35 | }; 36 | }) // dont check ruleId, which can be null 37 | // https://eslint.org/docs/developer-guide/nodejs-api.html#-lintmessage-type 38 | .filter(({ errored }) => (quiet ? errored : true)), 39 | }), 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/rules/es6-blacklist.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | // 箭头函数的箭头前后各留一个空格 4 | // @unessential 5 | 'arrow-spacing': ['warn', { before: true, after: true }], 6 | 7 | // generator 函数的 * 号前面无空格,后面有一个空格 8 | // @unessential 9 | 'generator-star-spacing': ['warn', { before: false, after: true }], 10 | 11 | // 避免箭头函数与比较操作符产生混淆 12 | // @unessential 13 | 'no-confusing-arrow': 'warn', 14 | 15 | // 回调函数使用箭头函数而不是匿名函数 16 | // @unessential 17 | 'prefer-arrow-callback': [ 18 | 'warn', 19 | { 20 | allowNamedFunctions: false, 21 | allowUnboundThis: true, 22 | }, 23 | ], 24 | 25 | // 优先使用 const,只有当变量会被重新赋值时才使用 let 26 | // @unessential 27 | 'prefer-const': [ 28 | 'warn', 29 | { 30 | destructuring: 'any', 31 | ignoreReadBeforeAssign: true, 32 | }, 33 | ], 34 | 35 | // 模板字符串中的大括号内部两侧无空格 36 | // @unessential 37 | 'template-curly-spacing': 'warn', 38 | 39 | // yield* 表达式的 * 号前面无空格,后面有一个空格 40 | // @unessential 41 | 'yield-star-spacing': ['warn', 'after'], 42 | 43 | // import 语句需要放到模块的最上方 44 | // @unessential 45 | 'import/first': 'warn', 46 | 47 | // 使用对象属性和方法的简写语法 48 | // @unessential 49 | 'object-shorthand': [ 50 | 'warn', 51 | 'always', 52 | { 53 | ignoreConstructors: false, 54 | avoidQuotes: true, 55 | }, 56 | ], 57 | 58 | // 使用 const 或 let 声明变量,不要使用 var 59 | // @unessential 60 | 'no-var': 'warn', 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /packages/eslint-plugin/rules/no-js-in-ts-project.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const RULE_NAME = 'no-js-in-ts-project'; 4 | 5 | const JS_REG = /\.jsx?$/; 6 | 7 | const DEFAULT_WHITE_LIST = [ 8 | 'commitlint.config.js', 9 | 'eslintrc.js', 10 | 'prettierrc.js', 11 | 'stylelintrc.js', 12 | ]; 13 | 14 | module.exports = { 15 | name: RULE_NAME, 16 | meta: { 17 | type: 'suggestion', 18 | fixable: null, 19 | messages: { 20 | noJSInTSProject: 'The "{{fileName}}" is not recommended in TS project', 21 | }, 22 | }, 23 | 24 | create(context) { 25 | const fileName = context.getFilename(); 26 | const extName = path.extname(fileName); 27 | const ruleOptions = context.options[0] || {}; 28 | let { whiteList = [], autoMerge = true } = ruleOptions; 29 | if (whiteList.length === 0) { 30 | whiteList = DEFAULT_WHITE_LIST; 31 | } else if (autoMerge) { 32 | whiteList = [...new Set([...DEFAULT_WHITE_LIST, ...whiteList])]; 33 | } 34 | const whiteListReg = new RegExp(`(${whiteList.join('|')})$`); 35 | 36 | if (!whiteListReg.test(fileName) && JS_REG.test(extName)) { 37 | context.report({ 38 | loc: { 39 | start: { 40 | line: 0, 41 | column: 0, 42 | }, 43 | end: { 44 | line: 0, 45 | column: 0, 46 | }, 47 | }, 48 | messageId: 'noJSInTSProject', 49 | data: { 50 | fileName, 51 | }, 52 | }); 53 | } 54 | 55 | // Necessary 56 | return {}; 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-encode", 3 | "version": "1.0.8", 4 | "main": "index.js", 5 | "description": "印客学院 JavaScript TypeScript Node 规范", 6 | "keywords": [ 7 | "encode", 8 | "javaScript", 9 | "typescript", 10 | "node", 11 | "lint" 12 | ], 13 | "scripts": { 14 | "lint": "eslint ./", 15 | "test": "mocha ./__tests__/*.test.js --timeout 5000", 16 | "print-config": "eslint --print-config ./index.js > ./print-config.json" 17 | }, 18 | "author": "chenghuai", 19 | "homepage": "https://github.com/encode-studio-fe/fe-spec#readme", 20 | "license": "ISC", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/encode-studio-fe/fe-spec.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/encode-studio-fe/fe-spec/issues" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.16.0", 30 | "@babel/eslint-parser": "^7.16.3", 31 | "@babel/preset-react": "^7.16.0", 32 | "@typescript-eslint/eslint-plugin": "^5.0.0", 33 | "@typescript-eslint/parser": "^5.0.0", 34 | "eslint": "^8.7.0", 35 | "eslint-config-egg": "^10.0.0", 36 | "eslint-import-resolver-typescript": "^2.5.0", 37 | "eslint-plugin-import": "^2.25.3", 38 | "eslint-plugin-jsx-a11y": "^6.3.1", 39 | "eslint-plugin-jsx-plus": "^0.1.0", 40 | "eslint-plugin-react": "^7.17.0", 41 | "eslint-plugin-react-hooks": "^4.2.0", 42 | "eslint-plugin-vue": "^7.3.0", 43 | "lodash": "^4.17.21", 44 | "mocha": "^8.2.1", 45 | "typescript": "^4.1.2", 46 | "vue-eslint-parser": "^7.3.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/eslint-plugin/rules/no-secret-info.js: -------------------------------------------------------------------------------- 1 | const RULE_NAME = 'no-secret-info'; 2 | 3 | const DEFAULT_DANGEROUS_KEYS = ['secret', 'token', 'password']; 4 | 5 | module.exports = { 6 | meta: { 7 | type: 'problem', 8 | fixable: null, 9 | messages: { 10 | noSecretInfo: 'Detect that the "{{secret}}" might be a secret token, Please check!', 11 | }, 12 | }, 13 | 14 | create(context) { 15 | const ruleOptions = context.options[0] || {}; 16 | let { dangerousKeys = [], autoMerge = true } = ruleOptions; 17 | if (dangerousKeys.length === 0) { 18 | dangerousKeys = DEFAULT_DANGEROUS_KEYS; 19 | } else if (autoMerge) { 20 | dangerousKeys = [...new Set(...DEFAULT_DANGEROUS_KEYS, ...dangerousKeys)]; 21 | } 22 | const reg = new RegExp(dangerousKeys.join('|')); 23 | 24 | return { 25 | Literal: function handleRequires(node) { 26 | if ( 27 | node.value && 28 | node.parent && 29 | ((node.parent.type === 'VariableDeclarator' && 30 | node.parent.id && 31 | node.parent.id.name && 32 | reg.test(node.parent.id.name.toLocaleLowerCase())) || 33 | (node.parent.type === 'Property' && 34 | node.parent.key && 35 | node.parent.key.name && 36 | reg.test(node.parent.key.name.toLocaleLowerCase()))) 37 | ) { 38 | context.report({ 39 | node, 40 | messageId: 'noSecretInfo', 41 | data: { 42 | secret: node.value, 43 | }, 44 | }); 45 | } 46 | }, 47 | }; 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /packages/eslint-config/__tests__/use-babel-eslint.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const eslint = require('eslint'); 3 | const path = require('path'); 4 | 5 | describe('test/use-babel-eslint.test.js', () => { 6 | it('babel-eslint parser run well for react', async () => { 7 | const configPath = './react.js'; 8 | const filePath = path.join(__dirname, './fixtures/use-babel-eslint.jsx'); 9 | 10 | const cli = new eslint.ESLint({ 11 | overrideConfigFile: configPath, 12 | useEslintrc: false, 13 | ignore: false, 14 | }); 15 | 16 | const results = await cli.lintFiles([filePath]); 17 | const { messages, errorCount, fatalErrorCount, warningCount } = results[0]; 18 | 19 | assert.equal(fatalErrorCount, 0); 20 | assert.equal(errorCount, 27); 21 | assert.equal(warningCount, 7); 22 | 23 | const errorReportedByReactPlugin = messages.filter((result) => { 24 | return result.ruleId && result.ruleId.indexOf('react/') !== -1; 25 | }); 26 | 27 | assert.notEqual(errorReportedByReactPlugin.length, 0); 28 | }); 29 | 30 | it('babel-eslint parser run well for vue', async () => { 31 | const configPath = './vue.js'; 32 | const filePath = path.join(__dirname, './fixtures/vue.vue'); 33 | 34 | const cli = new eslint.ESLint({ 35 | overrideConfigFile: configPath, 36 | useEslintrc: false, 37 | ignore: false, 38 | }); 39 | 40 | const results = await cli.lintFiles([filePath]); 41 | const { errorCount, fatalErrorCount, warningCount } = results[0]; 42 | 43 | assert.equal(fatalErrorCount, 0); 44 | assert.equal(errorCount, 4); 45 | assert.equal(warningCount, 0); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/eslint/getESLintConfig.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from 'eslint'; 2 | import fs from 'fs-extra'; 3 | import glob from 'glob'; 4 | import path from 'path'; 5 | import type { Config, PKG, ScanOptions } from '../../types'; 6 | import { ESLINT_FILE_EXT } from '../../utils/constants'; 7 | import { getESLintConfigType } from './getESLintConfigType'; 8 | 9 | /** 10 | * 获取 ESLint 配置 11 | */ 12 | export function getESLintConfig(opts: ScanOptions, pkg: PKG, config: Config): ESLint.Options { 13 | const { cwd, fix, ignore } = opts; 14 | const lintConfig: ESLint.Options = { 15 | cwd, 16 | fix, 17 | ignore, 18 | extensions: ESLINT_FILE_EXT, 19 | errorOnUnmatchedPattern: false, 20 | }; 21 | 22 | if (config.eslintOptions) { 23 | // 若用户传入了 eslintOptions,则用用户的 24 | Object.assign(lintConfig, config.eslintOptions); 25 | } else { 26 | // 根据扫描目录下有无lintrc文件,若无则使用默认的 lint 配置 27 | const lintConfigFiles = glob.sync('.eslintrc?(.@(js|yaml|yml|json))', { cwd }); 28 | if (lintConfigFiles.length === 0 && !pkg.eslintConfig) { 29 | lintConfig.resolvePluginsRelativeTo = path.resolve(__dirname, '../../'); 30 | lintConfig.useEslintrc = false; 31 | lintConfig.baseConfig = { 32 | extends: [ 33 | getESLintConfigType(cwd, pkg), 34 | // ESLint 不再管格式问题,直接使用 Prettier 进行格式化 35 | ...(config.enablePrettier ? ['prettier'] : []), 36 | ], 37 | }; 38 | } 39 | 40 | // 根据扫描目录下有无lintignore文件,若无则使用默认的 ignore 配置 41 | const lintIgnoreFile = path.resolve(cwd, '.eslintignore'); 42 | if (!fs.existsSync(lintIgnoreFile) && !pkg.eslintIgnore) { 43 | lintConfig.ignorePath = path.resolve(__dirname, '../config/_eslintignore.ejs'); 44 | } 45 | } 46 | 47 | return lintConfig; 48 | } 49 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/actions/update.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import ora from 'ora'; 3 | import log from '../utils/log'; 4 | import npmType from '../utils/npm-type'; 5 | import { PKG_NAME, PKG_VERSION } from '../utils/constants'; 6 | 7 | /** 8 | * 检查最新版本号 9 | */ 10 | const checkLatestVersion = async (): Promise => { 11 | const npm = await npmType; 12 | const latestVersion = execSync(`${npm} view ${PKG_NAME} version`).toString('utf-8').trim(); 13 | 14 | if (PKG_VERSION === latestVersion) return null; 15 | 16 | const compareArr = PKG_VERSION.split('.').map(Number); 17 | const beComparedArr = latestVersion.split('.').map(Number); 18 | 19 | // 依次比较版本号每一位大小 20 | for (let i = 0; i < compareArr.length; i++) { 21 | if (compareArr[i] > beComparedArr[i]) { 22 | return null; 23 | } else if (compareArr[i] < beComparedArr[i]) { 24 | return latestVersion; 25 | } 26 | } 27 | }; 28 | 29 | /** 30 | * 检查包的版本 31 | * @param install - 自动安装最新包 32 | */ 33 | export default async (install = true) => { 34 | const checking = ora(`[${PKG_NAME}] 正在检查最新版本...`); 35 | checking.start(); 36 | 37 | try { 38 | const npm = await npmType; 39 | const latestVersion = await checkLatestVersion(); 40 | checking.stop(); 41 | 42 | if (latestVersion && install) { 43 | const update = ora(`[${PKG_NAME}] 存在新版本,将升级至 ${latestVersion}`); 44 | 45 | update.start(); 46 | 47 | execSync(`${npm} i -g ${PKG_NAME}`); 48 | 49 | update.stop(); 50 | } else if (latestVersion) { 51 | log.warn( 52 | `最新版本为 ${latestVersion},本地版本为 ${PKG_VERSION},请尽快升级到最新版本。\n你可以执行 ${npm} install -g ${PKG_NAME}@latest 来安装此版本\n`, 53 | ); 54 | } else if (install) { 55 | log.info(`当前没有可用的更新`); 56 | } 57 | } catch (e) { 58 | checking.stop(); 59 | log.error(e); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /packages/eslint-plugin/rules/no-broad-semantic-versioning.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const RULE_NAME = 'no-broad-semantic-versioning'; 4 | 5 | module.exports = { 6 | name: RULE_NAME, 7 | meta: { 8 | type: 'problem', 9 | fixable: null, 10 | messages: { 11 | noBroadSemanticVersioning: 12 | 'The "{{dependencyName}}" is not recommended to use "{{versioning}}"', 13 | }, 14 | }, 15 | 16 | create(context) { 17 | if (path.basename(context.getFilename()) !== 'package.json') { 18 | return {}; 19 | } 20 | 21 | const cwd = context.getCwd(); 22 | 23 | return { 24 | Property: function handleRequires(node) { 25 | if ( 26 | node.key && 27 | node.key.value && 28 | (node.key.value === 'dependencies' || node.key.value === 'devDependencies') && 29 | node.value && 30 | node.value.properties 31 | ) { 32 | node.value.properties.forEach((property) => { 33 | if (property.key && property.key.value) { 34 | const dependencyName = property.key.value; 35 | const dependencyVersion = property.value.value; 36 | if ( 37 | // * 38 | dependencyVersion.indexOf('*') > -1 || 39 | // x.x 40 | dependencyVersion.indexOf('x') > -1 || 41 | // > x 42 | dependencyVersion.indexOf('>') > -1 43 | ) { 44 | context.report({ 45 | loc: property.loc, 46 | messageId: 'noBroadSemanticVersioning', 47 | data: { 48 | dependencyName, 49 | versioning: dependencyVersion, 50 | }, 51 | }); 52 | } 53 | } 54 | }); 55 | } 56 | }, 57 | }; 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/config/_vscode/settings.json.ejs: -------------------------------------------------------------------------------- 1 | { 2 | <%_ if (enableESLint) { _%> 3 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"], 4 | <%_ } _%> 5 | <%_ if (enableStylelint) { _%> 6 | "stylelint.validate": [<%- stylelintExt.map((ext) => `"${ext.replace(/^\./, '')}"`).join(',') %>], 7 | <%_ } _%> 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll.eslint": true<%= enableStylelint || enableMarkdownlint ? ',' : ''%> 10 | <%_ if (enableStylelint) { _%> 11 | "source.fixAll.stylelint": true<%= enableMarkdownlint ? ',' : ''%> 12 | <%_ } _%> 13 | <%_ if (enableMarkdownlint) { _%> 14 | "source.fixAll.markdownlint": true 15 | <%_ } _%> 16 | }, 17 | <%_ if (enablePrettier) { _%> 18 | "editor.defaultFormatter": "esbenp.prettier-vscode", 19 | "[javascript]": { 20 | "editor.defaultFormatter": "esbenp.prettier-vscode" 21 | }, 22 | "[javascriptreact]": { 23 | "editor.defaultFormatter": "esbenp.prettier-vscode" 24 | }, 25 | "[typescript]": { 26 | "editor.defaultFormatter": "esbenp.prettier-vscode" 27 | }, 28 | "[typescriptreact]": { 29 | "editor.defaultFormatter": "esbenp.prettier-vscode" 30 | }, 31 | "[vue]": { 32 | "editor.defaultFormatter": "esbenp.prettier-vscode" 33 | }, 34 | "[css]": { 35 | "editor.defaultFormatter": "esbenp.prettier-vscode" 36 | }, 37 | "[less]": { 38 | "editor.defaultFormatter": "esbenp.prettier-vscode" 39 | }, 40 | "[scss]": { 41 | "editor.defaultFormatter": "esbenp.prettier-vscode" 42 | }, 43 | "[html]": { 44 | "editor.defaultFormatter": "esbenp.prettier-vscode" 45 | }, 46 | "[json]": { 47 | "editor.defaultFormatter": "esbenp.prettier-vscode" 48 | }, 49 | "[jsonc]": { 50 | "editor.defaultFormatter": "esbenp.prettier-vscode" 51 | }, 52 | <%_ } _%> 53 | "editor.formatOnSave": <%= enablePrettier ? 'true' : 'false' %> 54 | } 55 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/test/cli.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | const execa = require('execa'); 4 | const packageJson = require('../package.json'); 5 | 6 | const cli = (args, options) => { 7 | return execa('node', [path.resolve(__dirname, '../lib/cli.js'), ...args], options); 8 | }; 9 | 10 | test('--version should output right version', async () => { 11 | const { stdout } = await cli(['--version']); 12 | expect(stdout).toBe(packageJson.version); 13 | }); 14 | 15 | describe(`'fix' command`, () => { 16 | const dir = path.resolve(__dirname, './fixtures/autofix'); 17 | const outputFilePath = path.resolve(dir, './temp/temp.js'); 18 | const errorFileContent = fs.readFileSync(path.resolve(dir, './semi-error.js'), 'utf8'); 19 | const expectedFileContent = fs.readFileSync(path.resolve(dir, './semi-expected.js'), 'utf8'); 20 | 21 | beforeEach(() => { 22 | fs.outputFileSync(outputFilePath, errorFileContent, 'utf8'); 23 | }); 24 | 25 | test('should autofix problematic code', async () => { 26 | await cli(['fix'], { 27 | cwd: path.dirname(`${dir}/result`), 28 | }); 29 | expect(fs.readFileSync(outputFilePath, 'utf8')).toEqual(expectedFileContent); 30 | }); 31 | 32 | afterEach(() => { 33 | fs.removeSync(`${dir}/temp`); 34 | }); 35 | }); 36 | 37 | describe(`'exec' command`, () => { 38 | const semverRegex = /(\d+)\.(\d+)\.(\d+)/; 39 | 40 | test(`'exec eslint' should work as expected`, async () => { 41 | const { stdout } = await cli(['exec', 'eslint', '--version']); 42 | expect(stdout).toMatch(semverRegex); 43 | }); 44 | 45 | test(`'exec stylelint' should work as expected`, async () => { 46 | const { stdout } = await cli(['exec', 'stylelint', '--version']); 47 | expect(stdout).toMatch(semverRegex); 48 | }); 49 | 50 | test(`'exec commitlint' should work as expected`, async () => { 51 | const { stdout } = await cli(['exec', 'commitlint', '--version']); 52 | expect(stdout).toMatch(semverRegex); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/utils/generate-template.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import _ from 'lodash'; 4 | import glob from 'glob'; 5 | import ejs from 'ejs'; 6 | import { 7 | ESLINT_IGNORE_PATTERN, 8 | STYLELINT_FILE_EXT, 9 | STYLELINT_IGNORE_PATTERN, 10 | MARKDOWN_LINT_IGNORE_PATTERN, 11 | } from './constants'; 12 | 13 | /** 14 | * vscode 配置合并 15 | * @param filepath 16 | * @param content 17 | */ 18 | const mergeVSCodeConfig = (filepath: string, content: string) => { 19 | // 不需要 merge 20 | if (!fs.existsSync(filepath)) return content; 21 | 22 | try { 23 | const targetData = fs.readJSONSync(filepath); 24 | const sourceData = JSON.parse(content); 25 | return JSON.stringify( 26 | _.mergeWith(targetData, sourceData, (target, source) => { 27 | if (Array.isArray(target) && Array.isArray(source)) { 28 | return [...new Set(source.concat(target))]; 29 | } 30 | }), 31 | null, 32 | 2, 33 | ); 34 | } catch (e) { 35 | return ''; 36 | } 37 | }; 38 | 39 | /** 40 | * 实例化模板 41 | * @param cwd 42 | * @param data 43 | * @param vscode 44 | */ 45 | export default (cwd: string, data: Record, vscode?: boolean) => { 46 | const templatePath = path.resolve(__dirname, '../config'); 47 | const templates = glob.sync(`${vscode ? '_vscode' : '**'}/*.ejs`, { cwd: templatePath }); 48 | for (const name of templates) { 49 | const filepath = path.resolve(cwd, name.replace(/\.ejs$/, '').replace(/^_/, '.')); 50 | let content = ejs.render(fs.readFileSync(path.resolve(templatePath, name), 'utf8'), { 51 | eslintIgnores: ESLINT_IGNORE_PATTERN, 52 | stylelintExt: STYLELINT_FILE_EXT, 53 | stylelintIgnores: STYLELINT_IGNORE_PATTERN, 54 | markdownLintIgnores: MARKDOWN_LINT_IGNORE_PATTERN, 55 | ...data, 56 | }); 57 | 58 | // 合并 vscode config 59 | if (/^_vscode/.test(name)) { 60 | content = mergeVSCodeConfig(filepath, content); 61 | } 62 | 63 | // 跳过空文件 64 | if (!content.trim()) continue; 65 | 66 | fs.outputFileSync(filepath, content, 'utf8'); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /packages/eslint-config/essential/react.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '../react', 4 | './rules/set-style-to-warn', 5 | './rules/blacklist', 6 | './rules/es6-blacklist', 7 | ].map(require.resolve), 8 | rules: { 9 | // 标签的属性有多行时,结束标签需另起一行 10 | // @unessential 11 | 'react/jsx-closing-bracket-location': ['warn', 'line-aligned'], 12 | 13 | // JSX 语法闭合标签的缩进和换行 14 | // @unessential 15 | 'react/jsx-closing-tag-location': 'warn', 16 | 17 | // JSX 行内属性间仅有一个空格 18 | // @unessential 19 | 'react/jsx-props-no-multi-spaces': 'warn', 20 | 21 | // JSX 属性的大括号内部两侧无空格 22 | // @unessential 23 | 'react/jsx-curly-spacing': ['warn', 'never', { allowMultiline: true }], 24 | 25 | // JSX 属性使用 2 个空格缩进 26 | // @unessential 27 | 'react/jsx-indent-props': ['off', 2], 28 | 29 | // 标签属性的换行,如果标签有多个属性,且存在换行,则每个属性都需要换行独占一行 30 | // @unessential 31 | 'react/jsx-max-props-per-line': ['warn', { maximum: 1, when: 'multiline' }], 32 | 33 | // 禁止使用已经废弃的方法 34 | // @unessential 35 | 'react/no-deprecated': 'warn', 36 | 37 | // 多行的 JSX 标签需用小括号包裹 38 | // @unessential 39 | 'react/jsx-wrap-multilines': [ 40 | 'warn', 41 | { 42 | declaration: true, 43 | assignment: true, 44 | return: true, 45 | arrow: true, 46 | }, 47 | ], 48 | 49 | // 设置第一个属性的位置。multiline-multiprop:如果JSX标签占用多行并且有多个属性,则第一个属性应始终放在新行上 50 | // @unessential 51 | 'react/jsx-first-prop-new-line': ['warn', 'multiline-multiprop'], 52 | 53 | // 不要在 JSX 属性的等号两边加空格 54 | // @unessential 55 | 'react/jsx-equals-spacing': ['warn', 'never'], 56 | 57 | // JSX 使用 2 个空格缩进 58 | // @unessential 59 | 'react/jsx-indent': ['off', 2], 60 | 61 | // 不要使用 findDOMNode,严格模式下已经弃用 62 | // @unessential 63 | 'react/no-find-dom-node': 'warn', 64 | 65 | // 自闭合标签的斜线前有且仅有一个空格 66 | // @unessential 67 | 'react/jsx-tag-spacing': [ 68 | 'warn', 69 | { 70 | closingSlash: 'never', 71 | beforeSelfClosing: 'always', 72 | afterOpening: 'never', 73 | }, 74 | ], 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/actions/scan.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import { doESLint, doMarkdownlint, doPrettier, doStylelint } from '../lints'; 4 | import type { Config, PKG, ScanOptions, ScanReport, ScanResult } from '../types'; 5 | import { PKG_NAME } from '../utils/constants'; 6 | 7 | export default async (options: ScanOptions): Promise => { 8 | const { cwd, fix, outputReport, config: scanConfig } = options; 9 | 10 | const readConfigFile = (pth: string): any => { 11 | const localPath = path.resolve(cwd, pth); 12 | return fs.existsSync(localPath) ? require(localPath) : {}; 13 | }; 14 | const pkg: PKG = readConfigFile('package.json'); 15 | const config: Config = scanConfig || readConfigFile(`${PKG_NAME}.config.js`); 16 | const runErrors: Error[] = []; 17 | let results: ScanResult[] = []; 18 | 19 | // prettier 20 | if (fix && config.enablePrettier !== false) { 21 | await doPrettier(options); 22 | } 23 | 24 | // eslint 25 | if (config.enableESLint !== false) { 26 | try { 27 | const eslintResults = await doESLint({ ...options, pkg, config }); 28 | results = results.concat(eslintResults); 29 | } catch (e) { 30 | runErrors.push(e); 31 | } 32 | } 33 | 34 | // stylelint 35 | if (config.enableStylelint !== false) { 36 | try { 37 | const stylelintResults = await doStylelint({ ...options, pkg, config }); 38 | results = results.concat(stylelintResults); 39 | } catch (e) { 40 | runErrors.push(e); 41 | } 42 | } 43 | 44 | // markdown 45 | if (config.enableMarkdownlint !== false) { 46 | try { 47 | const markdownlintResults = await doMarkdownlint({ ...options, pkg, config }); 48 | results = results.concat(markdownlintResults); 49 | } catch (e) { 50 | runErrors.push(e); 51 | } 52 | } 53 | 54 | // 生成报告报告文件 55 | if (outputReport) { 56 | const reportPath = path.resolve(process.cwd(), `./${PKG_NAME}-report.json`); 57 | fs.outputFile(reportPath, JSON.stringify(results, null, 2), () => {}); 58 | } 59 | 60 | return { 61 | results, 62 | errorCount: results.reduce((count, { errorCount }) => count + errorCount, 0), 63 | warningCount: results.reduce((count, { warningCount }) => count + warningCount, 0), 64 | runErrors, 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /packages/encode-fe-lint/src/lints/markdownlint/doMarkdownlint.ts: -------------------------------------------------------------------------------- 1 | import fg from 'fast-glob'; 2 | import { readFile, writeFile } from 'fs-extra'; 3 | import markdownlint, { LintError } from 'markdownlint'; 4 | import markdownlintRuleHelpers from 'markdownlint-rule-helpers'; 5 | import { extname, join } from 'path'; 6 | import { Config, PKG, ScanOptions } from '../../types'; 7 | import { MARKDOWN_LINT_FILE_EXT, MARKDOWN_LINT_IGNORE_PATTERN } from '../../utils/constants'; 8 | import { formatMarkdownlintResults } from './formatMarkdownlintResults'; 9 | import { getMarkdownlintConfig } from './getMarkdownlintConfig'; 10 | 11 | export interface DoMarkdownlintOptions extends ScanOptions { 12 | pkg: PKG; 13 | config?: Config; 14 | } 15 | 16 | export async function doMarkdownlint(options: DoMarkdownlintOptions) { 17 | let files: string[]; 18 | if (options.files) { 19 | files = options.files.filter((name) => MARKDOWN_LINT_FILE_EXT.includes(extname(name))); 20 | } else { 21 | const pattern = join( 22 | options.include, 23 | `**/*.{${MARKDOWN_LINT_FILE_EXT.map((t) => t.replace(/^\./, '')).join(',')}}`, 24 | ); 25 | files = await fg(pattern, { 26 | cwd: options.cwd, 27 | ignore: MARKDOWN_LINT_IGNORE_PATTERN, 28 | }); 29 | } 30 | const results = await markdownlint.promises.markdownlint({ 31 | ...getMarkdownlintConfig(options, options.pkg, options.config), 32 | files, 33 | }); 34 | // 修复 35 | if (options.fix) { 36 | await Promise.all( 37 | Object.keys(results).map((filename) => formatMarkdownFile(filename, results[filename])), 38 | ); 39 | for (const file in results) { 40 | if (!Object.prototype.hasOwnProperty.call(results, file)) continue; 41 | } 42 | } 43 | return formatMarkdownlintResults(results, options.quiet); 44 | } 45 | 46 | async function formatMarkdownFile(filename: string, errors: LintError[]) { 47 | const fixes = errors?.filter((error) => error.fixInfo); 48 | 49 | if (fixes?.length > 0) { 50 | const originalText = await readFile(filename, 'utf8'); 51 | const fixedText = markdownlintRuleHelpers.applyFixes(originalText, fixes); 52 | if (originalText !== fixedText) { 53 | await writeFile(filename, fixedText, 'utf8'); 54 | return errors.filter((error) => !error.fixInfo); 55 | } 56 | } 57 | return errors; 58 | } 59 | -------------------------------------------------------------------------------- /docs/npm/eslint-plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: eslint-plugin-encode 3 | categories: 4 | - 工程规范 5 | tags: 6 | - 工程规范 7 | author: 8 | name: 澄怀 9 | link: https://github.com/encode-studio-fe/fe-spec 10 | --- 11 | 12 | # eslint-plugin-encode 13 | 14 | 除了本包,你需要同时安装 [ESlint](https://eslint.org/) 15 | 16 | ```shell 17 | $ npm install eslint-plugin-encode eslint --save-dev 18 | ``` 19 | 20 | ## 使用 21 | 22 | ### 引入插件 23 | 24 | ```js 25 | // .eslintrc.js 26 | module.exports = { 27 | plugin: ['eslint-plugin-encode'], 28 | rules: { 29 | 'eslint-plugin-encode/no-secret-info': 'error', 30 | }, 31 | }; 32 | ``` 33 | 34 | ### 使用 presets 35 | 36 | ```js 37 | // .eslintrc.js 38 | module.exports = { 39 | extends: 'plugin:eslint-plugin-encode/recommended', 40 | }; 41 | ``` 42 | 43 | ## 支持的规则 44 | 45 | ### `no-broad-semantic-versioning` 46 | 47 | 不要在 `package.json` 中使用太过宽泛的版本指定方式,包括 `*`、`x` 和 `> x` 。 48 | 49 | #### 规则内容 50 | 51 | 参照 [https://docs.npmjs.com/about-semantic-versioning](https://docs.npmjs.com/about-semantic-versioning)。 52 | 53 | 使用 `*`、 `x` 和 `> x` 指定版本会被警告。 54 | 55 | ### `no-http-url` 56 | 57 | 推荐将 HTTP 链接换为 HTTPS 链接。 58 | 59 | #### 规则内容 60 | 61 | **错误代码**示例: 62 | 63 | ```js 64 | var test = 'http://chenghuai.com'; 65 | var jsx = ; 66 | ``` 67 | 68 | #### 何时不适用 69 | 70 | 如果你的网站只支持 HTTP 时。 71 | 72 | ### `no-js-in-ts-project` 73 | 74 | 不推荐在项目中同时存在 `JS` 和 `TS` 文件。 75 | 76 | #### 规则内容 77 | 78 | **错误示例**,TS 项目中包含 JS 文件: 79 | 80 | ```Bash 81 | . 82 | ├── index.ts 83 | ├── home.js 84 | └── tsconfig.json 85 | ``` 86 | 87 | **正确示例**: 88 | 89 | ```Bash 90 | . 91 | ├── index.ts 92 | ├── home.ts 93 | └── tsconfig.json 94 | ``` 95 | 96 | #### 规则选项 97 | 98 | 默认当存在 `commitlint.config.js`, `eslintrc.js`, `prettierrc.js`, `stylelintrc.js` 文件时不会报错,支持自定义设置文件白名单。 99 | 100 | ### `no-secret-info` 101 | 102 | 不在代码中直接通过纯文本值设置 `password` `token` 和 `secret` 信息。 103 | 104 | #### 规则内容 105 | 106 | 在包含 `password` `token` and `secret` 名称的 key 中禁止使用纯文本值。 107 | 108 | **错误**代码示例: 109 | 110 | ```js 111 | var accessKeySecret = 'xxxx'; 112 | 113 | var client = { 114 | accessKeyToken: 'xxxx', 115 | }; 116 | ``` 117 | 118 | **正确**代码示例: 119 | 120 | ```js 121 | var accessKeySecret = process.env.ACCESS_KEY_SECRET; 122 | 123 | var client = { 124 | accessKeyToken: process.env.ACCESS_KEY_SECRET, 125 | }; 126 | ``` 127 | -------------------------------------------------------------------------------- /packages/markdownlint-config/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json", 3 | "default": true, 4 | "ul-style": { 5 | "style": "dash" 6 | }, 7 | "no-trailing-spaces": { 8 | "br_spaces": 0, 9 | "list_item_empty_lines": false 10 | }, 11 | "list-marker-space": false, 12 | "line-length": false, 13 | "no-inline-html": false, 14 | "no-duplicate-header": false, 15 | "proper-names": { 16 | "names": [ 17 | "JavaScript", 18 | "HTML", 19 | "CSS", 20 | "AJAX", 21 | "JSON", 22 | "DOM", 23 | "BOM", 24 | "Less", 25 | "Sass", 26 | "SCSS", 27 | "HTTP", 28 | "HTTPS", 29 | "WebSocket", 30 | "ECMAScript", 31 | "ECMAScript 2015", 32 | "ECMAScript 6", 33 | "ES6", 34 | "ES2015", 35 | "jQuery", 36 | "jQuery Mobile", 37 | "React", 38 | "React Native", 39 | "Bootstrap", 40 | "Gulp", 41 | "Grunt", 42 | "webpack", 43 | "Yeoman", 44 | "npm", 45 | "spm", 46 | "Babel", 47 | "Mocha", 48 | "Jasmine", 49 | "PHP", 50 | "Java", 51 | "Node.js", 52 | "NodeJS", 53 | "MySQL", 54 | "MongoDB", 55 | "Redis", 56 | "Apache", 57 | "Nginx", 58 | "NGINX", 59 | "GitHub", 60 | "GitLab", 61 | "GitCafe", 62 | "Chrome", 63 | "Firefox", 64 | "Safari", 65 | "Internet Explore", 66 | "IE", 67 | "IE 7", 68 | "Opera", 69 | "UC", 70 | "Android", 71 | "iOS", 72 | "Windows", 73 | "OS X", 74 | "Ubuntu", 75 | "Linux", 76 | "Debian", 77 | "PC", 78 | "Mobile", 79 | "H5", 80 | "MacBook", 81 | "MacBook Pro", 82 | "MacBook Air", 83 | "iMac", 84 | "Mac Pro", 85 | "iPad", 86 | "Mac mini", 87 | "iPad Air", 88 | "iPad Air 2", 89 | "iPad mini", 90 | "iPhone", 91 | "iPhone 6s", 92 | "iPhone 6s Plus", 93 | "Apple Watch", 94 | "Alibaba", 95 | "Taobao", 96 | "Google", 97 | "Alphabet", 98 | "Apple", 99 | "Microsoft", 100 | "Yahoo", 101 | "FPS", 102 | "UI", 103 | "URL", 104 | "URI", 105 | "URLs", 106 | "URIs", 107 | "Visual Studio Code" 108 | ], 109 | "code_blocks": false 110 | } 111 | } -------------------------------------------------------------------------------- /packages/eslint-config/rules/vue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本文件的规则由 eslint-plugin-vue 提供,使用 vue-eslint-parser 作为 parser 3 | * @link https://eslint.vuejs.org/rules/ 4 | */ 5 | 6 | module.exports = { 7 | parser: 'vue-eslint-parser', 8 | plugins: ['vue'], 9 | rules: { 10 | // 给 template 提供 eslint-disable 的能力,支持如下注释: 11 | // eslint-disable,eslint-enable,eslint-disable-line,eslint-disable-next-line 12 | 'vue/comment-directive': 'error', 13 | 14 | // 本条是对JS规范 no-unused-vars 的补充,防止变量被错误地标记为未使用 15 | 'vue/jsx-uses-vars': 'error', 16 | 17 | // 组件的 data 必须是一个函数 18 | 'vue/no-shared-component-data': 'error', 19 | 20 | // Prop 定义类型应该是构造函数 21 | 'vue/require-prop-type-constructor': 'error', 22 | 23 | // Prop 的默认值必须匹配它的类型 24 | 'vue/require-valid-default-prop': 'error', 25 | 26 | // 为 v-for 设置键值 27 | 'vue/require-v-for-key': 'error', 28 | 29 | // 避免 v-if 和 v-for 用在一起 30 | 'vue/no-use-v-if-with-v-for': 'warn', 31 | 32 | // 计算属性禁止包含异步方法 33 | 'vue/no-async-in-computed-properties': 'error', 34 | 35 | // 禁止在对象字面量中出现重复的键 36 | 'vue/no-dupe-keys': 'error', 37 | 38 | // 禁止出现重复的属性 39 | 'vue/no-duplicate-attributes': 'error', 40 | 41 | // 禁止出现语法错误 42 | // @link https://html.spec.whatwg.org/multipage/parsing.html#parse-errors 43 | 'vue/no-parsing-error': [ 44 | 'error', 45 | { 46 | 'x-invalid-end-tag': false, 47 | 'invalid-first-character-of-tag-name': false, 48 | }, 49 | ], 50 | 51 | // 禁止使用 vue 中的关键字 52 | 'vue/no-reserved-keys': 'error', 53 | 54 | // 禁止在计算属性中对属性修改 55 | 'vue/no-side-effects-in-computed-properties': 'error', 56 | 57 | // 禁止