├── lerna.json ├── .eslintignore ├── .prettierignore ├── .husky ├── pre-commit └── commit-msg ├── packages ├── spec │ ├── .stylelintignore │ ├── examples │ │ ├── rax-ts │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── rax │ │ │ ├── index.css │ │ │ └── index.jsx │ │ ├── common │ │ │ └── index.js │ │ ├── common-ts │ │ │ └── index.ts │ │ ├── vue │ │ │ └── index.vue │ │ ├── vue-ts │ │ │ └── index.vue │ │ ├── react │ │ │ ├── index.module.scss │ │ │ └── index.jsx │ │ └── react-ts │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── src │ │ ├── commitlint │ │ │ ├── rax.js │ │ │ ├── vue.js │ │ │ ├── common.js │ │ │ └── react.js │ │ ├── stylelint │ │ │ ├── rax.js │ │ │ ├── vue.js │ │ │ ├── common.js │ │ │ └── react.js │ │ ├── prettier │ │ │ ├── rax.js │ │ │ ├── vue.js │ │ │ ├── common.js │ │ │ └── react.js │ │ ├── eslint │ │ │ ├── react.js │ │ │ ├── vue.js │ │ │ ├── common.js │ │ │ ├── react-ts.js │ │ │ ├── rax.js │ │ │ ├── common-ts.js │ │ │ ├── rax-ts.js │ │ │ └── vue-ts.js │ │ ├── deepmerge.js │ │ ├── getRaxEslintConfig.js │ │ └── index.js │ ├── .eslintrc.rax.js │ ├── .eslintrc.vue.js │ ├── .eslintrc.common.js │ ├── .eslintrc.rax-ts.js │ ├── .eslintrc.react.js │ ├── .eslintrc.vue-ts.js │ ├── .stylelintrc.js │ ├── .eslintrc.common-ts.js │ ├── .eslintrc.react-ts.js │ ├── tsconfig.json │ ├── .editorconfig │ ├── CHANGELOG.md │ ├── test │ │ └── index.test.js │ ├── package.json │ └── README.md └── eslint-plugin-best-practices │ ├── src │ ├── docsUrl.js │ ├── configs │ │ ├── rax.js │ │ ├── react.js │ │ ├── rax-ts.js │ │ ├── react-ts.js │ │ └── common.js │ ├── index.js │ └── rules │ │ ├── deps-no-resolutions.js │ │ ├── no-http-url.js │ │ ├── recommend-functional-component.js │ │ ├── deps-no-ice-scripts.js │ │ ├── no-js-in-ts-project.js │ │ ├── recommend-update-rax.js │ │ ├── deps-no-router-library.js │ │ ├── no-lowercase-component-name.js │ │ ├── recommend-add-line-height-unit.js │ │ ├── no-broad-semantic-versioning.js │ │ └── recommend-polyfill.js │ ├── docs │ └── rules │ │ ├── no-http-url.md │ │ ├── deps-no-resolutions.md │ │ ├── no-broad-semantic-versioning.md │ │ ├── deps-no-router-library.md │ │ ├── recommend-update-rax.md │ │ ├── no-js-in-ts-project.md │ │ ├── recommend-functional-component.md │ │ ├── deps-no-ice-scripts.md │ │ ├── no-lowercase-component-name.md │ │ ├── recommend-polyfill.md │ │ └── recommend-add-line-height-unit.md │ ├── CHANGELOG.md │ ├── test │ └── src │ │ └── rules │ │ ├── no-js-in-ts-project.test.js │ │ ├── deps-no-resolutions.test.js │ │ ├── recommend-update-rax.test.js │ │ ├── deps-no-ice-scripts.test.js │ │ ├── no-broad-semantic-versioning.test.js │ │ ├── deps-no-router-library.test.js │ │ ├── no-http-url.test.js │ │ ├── recommend-functional-component.test.js │ │ ├── recommend-add-line-height-unit.test.js │ │ ├── no-lowercase-component-name.test.js │ │ └── recommend-polyfill.test.js │ ├── package.json │ └── README.md ├── .prettierrc.js ├── commitlint.config.js ├── tsconfig.json ├── .gitignore ├── .github ├── workflows │ ├── ci.yml │ └── auto-publisher.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .eslintrc.js ├── scripts ├── publish.js └── getPackageInfos.js ├── package.json └── README.md /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | dist/ 4 | build/ 5 | coverage/ 6 | demo/ 7 | examples/ 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | dist/ 4 | build/ 5 | coverage/ 6 | demo/ 7 | examples/ 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint && npm run test 5 | -------------------------------------------------------------------------------- /packages/spec/.stylelintignore: -------------------------------------------------------------------------------- 1 | # 忽略目录 2 | build/ 3 | tests/ 4 | demo/ 5 | 6 | # node 覆盖率文件 7 | coverage/ 8 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /packages/spec/examples/rax-ts/index.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 200rpx; 3 | height: 180rpx; 4 | margin-bottom: 20rpx; 5 | } 6 | -------------------------------------------------------------------------------- /packages/spec/examples/rax/index.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 200rpx; 3 | height: 180rpx; 4 | margin-bottom: 20rpx; 5 | } 6 | -------------------------------------------------------------------------------- /packages/spec/src/commitlint/rax.js: -------------------------------------------------------------------------------- 1 | // commitlint config for rax project 2 | module.exports = { 3 | extends: 'ali', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/spec/src/commitlint/vue.js: -------------------------------------------------------------------------------- 1 | // commitlint config for vue project 2 | module.exports = { 3 | extends: 'ali', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.rax.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('rax'); 4 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.vue.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('vue'); 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const { getPrettierConfig } = require('./packages/spec/src/'); 2 | 3 | module.exports = getPrettierConfig('react'); 4 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.common.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('common'); 4 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.rax-ts.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('rax-ts'); 4 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.react.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('react'); 4 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.vue-ts.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('vue-ts'); 4 | -------------------------------------------------------------------------------- /packages/spec/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const { getStylelintConfig } = require('./src'); 2 | 3 | module.exports = getStylelintConfig('react'); 4 | -------------------------------------------------------------------------------- /packages/spec/examples/common/index.js: -------------------------------------------------------------------------------- 1 | const foo = [1, 2]; 2 | console.log(foo); 3 | 4 | const t = { s: 1 }; 5 | console.log(t['s']); 6 | -------------------------------------------------------------------------------- /packages/spec/src/commitlint/common.js: -------------------------------------------------------------------------------- 1 | // commitlint config for common project 2 | module.exports = { 3 | extends: 'ali', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.common-ts.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('common-ts'); 4 | -------------------------------------------------------------------------------- /packages/spec/.eslintrc.react-ts.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./src'); 2 | 3 | module.exports = getESLintConfig('react-ts'); 4 | -------------------------------------------------------------------------------- /packages/spec/src/commitlint/react.js: -------------------------------------------------------------------------------- 1 | // commitlint config for ice and react project 2 | module.exports = { 3 | extends: 'ali', 4 | }; 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const { getCommitlintConfig } = require('./packages/spec/src/'); 2 | 3 | module.exports = getCommitlintConfig('react'); 4 | -------------------------------------------------------------------------------- /packages/spec/src/stylelint/rax.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/stylelint-config-ali 2 | // stylelint config for rax project 3 | module.exports = { 4 | extends: ['stylelint-config-ali'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/spec/src/stylelint/vue.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/stylelint-config-ali 2 | // stylelint config for vue project 3 | module.exports = { 4 | extends: ['stylelint-config-ali'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/spec/src/stylelint/common.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/stylelint-config-ali 2 | // stylelint config for common project 3 | module.exports = { 4 | extends: ['stylelint-config-ali'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/spec/src/prettier/rax.js: -------------------------------------------------------------------------------- 1 | // prettier config for rax project 2 | module.exports = { 3 | printWidth: 120, 4 | tabWidth: 2, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/spec/src/prettier/vue.js: -------------------------------------------------------------------------------- 1 | // prettier config for vue project 2 | module.exports = { 3 | printWidth: 120, 4 | tabWidth: 2, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/spec/src/stylelint/react.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/stylelint-config-ali 2 | // stylelint config for ice and react project 3 | module.exports = { 4 | extends: ['stylelint-config-ali'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/spec/src/prettier/common.js: -------------------------------------------------------------------------------- 1 | // prettier config for common project 2 | module.exports = { 3 | printWidth: 120, 4 | tabWidth: 2, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/spec/src/prettier/react.js: -------------------------------------------------------------------------------- 1 | // prettier config for ice and react project 2 | module.exports = { 3 | printWidth: 120, 4 | tabWidth: 2, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'all', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/spec/examples/common-ts/index.ts: -------------------------------------------------------------------------------- 1 | interface AppDeveloper { 2 | name: string; 3 | id: number; 4 | } 5 | 6 | const bar = [1, 2]; 7 | const one: AppDeveloper = { name: 'hello', id: 1 }; 8 | 9 | const t = { s: 1 }; 10 | console.log(t['s']); 11 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/docsUrl.js: -------------------------------------------------------------------------------- 1 | const repoUrl = 'https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices'; 2 | 3 | module.exports = function docsUrl(ruleName) { 4 | return `${repoUrl}/docs/rules/${ruleName}.md`; 5 | }; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esNext", 4 | "target": "es2015", 5 | "outDir": "build", 6 | "jsx": "preserve", 7 | "jsxFactory": "createElement", 8 | "moduleResolution": "node", 9 | "baseUrl": "." 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/spec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esNext", 4 | "target": "es2015", 5 | "outDir": "build", 6 | "jsx": "preserve", 7 | "jsxFactory": "createElement", 8 | "moduleResolution": "node", 9 | "baseUrl": "." 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/spec/examples/rax/index.jsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'rax'; 2 | import Image from 'rax-image'; 3 | 4 | import './index.css'; 5 | 6 | const unusedVar = 1; 7 | 8 | export default (props) => { 9 | const { uri } = props; 10 | const source = { uri }; 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/spec/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [makefile] 15 | indent_style = tab 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | .DS_Store 4 | npm-debug.log 5 | lerna-debug.log 6 | npm-debug.log* 7 | es/ 8 | lib/ 9 | dist/ 10 | build/ 11 | coverage/ 12 | node_modules/ 13 | examples/test 14 | .idea/ 15 | .history/ 16 | packages/**/lib/ 17 | packages/**/dist/ 18 | packages/**/build/ 19 | packages/**/coverage/ 20 | packages/**/node_modules/ 21 | 22 | package-lock.json 23 | yarn.lock -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/configs/rax.js: -------------------------------------------------------------------------------- 1 | const commonConfig = require('./common'); 2 | const { deepmerge } = require('@iceworks/spec'); 3 | 4 | module.exports = deepmerge(commonConfig, { 5 | rules: { 6 | 'max-lines': ['warn', { max: 500 }], 7 | 'no-plusplus': 'off', 8 | 'no-return-await': 'off', 9 | 'no-unused-vars': 'warn', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/no-http-url.md: -------------------------------------------------------------------------------- 1 | # no-http-url 2 | 3 | Recommended the http url switch to HTTPS. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```js 10 | var test = 'http://test.com'; 11 | var jsx = ; 12 | ``` 13 | 14 | ## When Not To Use It 15 | 16 | If your website only support http. 17 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/deps-no-resolutions.md: -------------------------------------------------------------------------------- 1 | # deps-no-resolutions 2 | 3 | It is not recommended to use resolutions to lock the version. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```json 10 | { 11 | "resolutions": { 12 | "package-a": "1.0.0", 13 | "package-b": "2.0.0", 14 | "package-c": "3.5.2" 15 | } 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/spec/examples/rax-ts/index.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'rax'; 2 | import Image from 'rax-image'; 3 | 4 | import './index.css'; 5 | 6 | interface LogoProps { 7 | uri: string; 8 | } 9 | 10 | const unusedVar = 1; 11 | 12 | export default (props: LogoProps) => { 13 | const { uri } = props; 14 | const source = { uri }; 15 | return ; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/no-broad-semantic-versioning.md: -------------------------------------------------------------------------------- 1 | # no-broad-semantic-versioning 2 | 3 | package.json is not recommended to use \*, x and > x in a wide range of semantic versioning. 4 | 5 | ## Rule Details 6 | 7 | See [https://docs.npmjs.com/about-semantic-versioning](https://docs.npmjs.com/about-semantic-versioning). 8 | 9 | The package's semantic versioning with `*`, `x` and `> x` will be warned. 10 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/react.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for ice and react project 3 | module.exports = { 4 | extends: [ 5 | require.resolve('eslint-config-ali/react'), 6 | // For some ci and jest test env, we chose require.resolve instead 'plugin:@iceworks/best-practices/react' 7 | require.resolve('@iceworks/eslint-plugin-best-practices/src/configs/react'), 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/vue.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for Vue project 3 | module.exports = { 4 | extends: [ 5 | require.resolve('eslint-config-ali/vue'), 6 | ], 7 | rules: { 8 | // Change error to warn 9 | 'semi': 'warn', 10 | 'eol-last': 'warn', 11 | 'quote-props': 'warn', 12 | 'no-unused-vars': 'warn', 13 | 'dot-notation': 'off', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: 10 17 | registry-url: https://registry.npmjs.org/ 18 | - run: npm i 19 | - run: npm run ci 20 | 21 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/common.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for common js project 3 | module.exports = { 4 | extends: [ 5 | require.resolve('eslint-config-ali'), 6 | ], 7 | rules: { 8 | // Change error to warn 9 | 'semi': 'warn', 10 | 'eol-last': 'warn', 11 | 'quote-props': 'warn', 12 | 'no-unused-vars': 'warn', 13 | 'dot-notation': 'off', 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/deps-no-router-library.md: -------------------------------------------------------------------------------- 1 | # deps-no-router-library 2 | 3 | It is not recommended to directly rely on routing libraries in [rax](https://rax.js.org/) and [ice](https://ice.work/) project, such as react-router-dom, react-router. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```json 10 | { 11 | "dependencies": { 12 | "react-router": "^5.2.0" 13 | } 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/configs/react.js: -------------------------------------------------------------------------------- 1 | const commonConfig = require('./common'); 2 | const { deepmerge } = require('@iceworks/spec'); 3 | 4 | module.exports = deepmerge(commonConfig, { 5 | rules: { 6 | 'max-len': ['warn', { code: 150 }], 7 | 'max-lines': ['warn', { max: 500 }], 8 | 'no-plusplus': 'off', 9 | 'no-return-await': 'off', 10 | 'no-unused-vars': 'warn', 11 | 'react/prop-types': 'off', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/recommend-update-rax.md: -------------------------------------------------------------------------------- 1 | # recommend-update-rax 2 | 3 | Rax version < 1.0 , recommend to update Rax. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```json 10 | { 11 | "dependencies": { 12 | "rax": "^0.6.0" 13 | } 14 | } 15 | ``` 16 | 17 | Examples of **correct** code for this rule: 18 | 19 | ```json 20 | { 21 | "dependencies": { 22 | "rax": "^1.1.0" 23 | } 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/spec/examples/vue/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/react-ts.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for ice TypeScript and react TypeScript project 3 | module.exports = { 4 | extends: [ 5 | require.resolve('eslint-config-ali/typescript/react'), 6 | // For some ci and jest test env, we chose require.resolve instead 'plugin:@iceworks/best-practices/react-ts' 7 | require.resolve('@iceworks/eslint-plugin-best-practices/src/configs/react-ts'), 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/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/spec/src/eslint/rax.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for Rax project 3 | const getRaxEslintConfig = require('../getRaxEslintConfig'); 4 | 5 | module.exports = getRaxEslintConfig({ 6 | extends: [ 7 | require.resolve('eslint-config-ali/rax'), 8 | // For some ci and jest test env, we chose require.resolve instead 'plugin:@iceworks/best-practices/rax' 9 | require.resolve('@iceworks/eslint-plugin-best-practices/src/configs/rax'), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/no-js-in-ts-project.md: -------------------------------------------------------------------------------- 1 | # no-js-in-ts-project 2 | 3 | It is not recommended to use js and ts files at the same time 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** directory for this rule:(contains xx.js in ts project) 8 | 9 | ```Bash 10 | . 11 | ├── index.ts 12 | ├── home.js 13 | └── tsconfig.json 14 | ``` 15 | 16 | Examples of **correct** code for this rule: 17 | 18 | ```Bash 19 | . 20 | ├── index.ts 21 | ├── home.ts 22 | └── tsconfig.json 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/configs/rax-ts.js: -------------------------------------------------------------------------------- 1 | const commonConfig = require('./common'); 2 | const { deepmerge } = require('@iceworks/spec'); 3 | 4 | module.exports = deepmerge(commonConfig, { 5 | rules: { 6 | 'max-lines': ['warn', { max: 500 }], 7 | 'no-plusplus': 'off', 8 | 'no-return-await': 'off', 9 | 'semi': 'off', 10 | '@typescript-eslint/dot-notation': 'off', 11 | '@typescript-eslint/semi': 'warn', 12 | '@typescript-eslint/no-unused-vars': 'warn', 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # changelog 2 | 3 | ## 0.2.11 4 | - Remove `no-secret-info`. 5 | ## 0.2.10 6 | - Update stylistic rules `dot-notation` and `@typescript-eslint/dot-notation` from error to off. 7 | 8 | ## 0.2.9 9 | 10 | - Update stylistic rules `semi` `eol-last` and `quote-props` from error to warn. 11 | 12 | ## 0.2.8 13 | 14 | - Update ESLint from 6.x to 7.x 15 | - Overrides `**/__tests__/*.{j,t}s?(x)`, `**/__tests__/*.{j,t}s?(x)` and `**/test/*.{j,t}s?(x)` env config `jest: true` 16 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/recommend-functional-component.md: -------------------------------------------------------------------------------- 1 | # recommend-functional-component 2 | 3 | It is not recommended to use class component. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```js 10 | class App extends React.Component { 11 | render() { 12 | return

hello world

; 13 | } 14 | } 15 | ``` 16 | 17 | Examples of **correct** code for this rule: 18 | 19 | ```js 20 | const App = () => { 21 | return

hello world

; 22 | }; 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/common-ts.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for common ts project 3 | module.exports = { 4 | extends: [ 5 | require.resolve('eslint-config-ali/typescript'), 6 | ], 7 | rules: { 8 | // Change error to warn 9 | 'semi': 'off', 10 | '@typescript-eslint/semi': 'warn', 11 | 'eol-last': 'warn', 12 | 'quote-props': 'warn', 13 | '@typescript-eslint/no-unused-vars': 'warn', 14 | '@typescript-eslint/dot-notation': 'off', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/rax-ts.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for Rax TypeScript project 3 | const getRaxEslintConfig = require('../getRaxEslintConfig'); 4 | 5 | module.exports = getRaxEslintConfig({ 6 | extends: [ 7 | require.resolve('eslint-config-ali/typescript/rax'), 8 | // For some ci and jest test env, we chose require.resolve instead 'plugin:@iceworks/best-practices/rax-ts' 9 | require.resolve('@iceworks/eslint-plugin-best-practices/src/configs/rax-ts'), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /packages/spec/src/eslint/vue-ts.js: -------------------------------------------------------------------------------- 1 | // https://www.npmjs.com/package/eslint-config-ali 2 | // ESlint config for Vue TypeScript project 3 | module.exports = { 4 | extends: [ 5 | require.resolve('eslint-config-ali/typescript/vue'), 6 | ], 7 | rules: { 8 | // Change error to warn 9 | 'semi': 'off', 10 | '@typescript-eslint/semi': 'warn', 11 | 'eol-last': 'warn', 12 | 'quote-props': 'warn', 13 | '@typescript-eslint/no-unused-vars': 'warn', 14 | '@typescript-eslint/dot-notation': 'off', 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/deps-no-ice-scripts.md: -------------------------------------------------------------------------------- 1 | # deps-no-ice-scripts 2 | 3 | It is not recommended to use ice-scripts, the new version is ice.js. See [https://ice.work/](https://ice.work/). 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```json 10 | { 11 | "devDependencies": { 12 | "ice.js": "^1.0.0" 13 | } 14 | } 15 | ``` 16 | 17 | Examples of **correct** code for this rule: 18 | 19 | ```json 20 | { 21 | "devDependencies": { 22 | "ice-script": "^1.0.0" 23 | } 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/configs/react-ts.js: -------------------------------------------------------------------------------- 1 | const commonConfig = require('./common'); 2 | const { deepmerge } = require('@iceworks/spec'); 3 | 4 | module.exports = deepmerge(commonConfig, { 5 | rules: { 6 | 'max-len': ['error', { code: 150 }], 7 | 'max-lines': ['warn', { max: 500 }], 8 | 'no-plusplus': 'off', 9 | 'no-return-await': 'off', 10 | 'react/prop-types': 'off', 11 | 'semi': 'off', 12 | '@typescript-eslint/dot-notation': 'off', 13 | '@typescript-eslint/semi': 'warn', 14 | '@typescript-eslint/no-unused-vars': 'warn', 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/spec/src/deepmerge.js: -------------------------------------------------------------------------------- 1 | module.exports = function (target, source) { 2 | // deep clone 3 | const newObj = JSON.parse(JSON.stringify(target)); 4 | 5 | Object.keys(source).forEach((key) => { 6 | const type = Object.prototype.toString.call(source[key]); 7 | 8 | if (type === '[object Array]') { 9 | newObj[key] = [...(target[key] || []), ...source[key]]; 10 | } else if (type === '[object Object]') { 11 | newObj[key] = { ...(target[key] || {}), ...source[key] }; 12 | } else { 13 | newObj[key] = source[key]; 14 | } 15 | }); 16 | 17 | return newObj; 18 | }; 19 | -------------------------------------------------------------------------------- /.github/workflows/auto-publisher.yml: -------------------------------------------------------------------------------- 1 | name: Auto Publisher 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 10 16 | registry-url: https://registry.npmjs.org/ 17 | - run: | 18 | npm i 19 | npm run setup 20 | npm run publish 21 | env: 22 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 23 | REGISTRY: https://registry.npmjs.org 24 | -------------------------------------------------------------------------------- /packages/spec/examples/vue-ts/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 23 | 24 | 33 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/no-lowercase-component-name.md: -------------------------------------------------------------------------------- 1 | # no-lowercase-component-name 2 | 3 | It is not recommended to name components in lower case. 4 | See: https://github.com/airbnb/javascript/tree/master/react#naming 5 | 6 | ## Rule Details 7 | 8 | Examples of **incorrect** code for this rule: 9 | 10 | ```jsx 11 | // src/components/app/index.jsx 12 | const app = () => { 13 | return

hello world

; 14 | }; 15 | 16 | export default app; 17 | ``` 18 | 19 | Examples of **correct** code for this rule: 20 | 21 | ```jsx 22 | // src/components/App/index.jsx 23 | const App = () => { 24 | return

hello world

; 25 | }; 26 | 27 | export default App; 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/recommend-polyfill.md: -------------------------------------------------------------------------------- 1 | # recommend-polyfill 2 | 3 | With the updating of operating systems and browsers, more and more new features are beginning to be supported. 4 | While bringing convenience to front-end development and technical solution design, some compatibility issues will also arise. 5 | 6 | Recommend API which not supported in iOS 9 to add polyfill file. 7 | 8 | ## Rule Details 9 | 10 | Examples of **incorrect** code for this rule: 11 | 12 | ```js 13 | navigator.sendBeacon('xxxx'); 14 | ``` 15 | 16 | navigator.sendBeacon is not support in iOS 9. 17 | 18 | Examples of **correct** code for this rule: 19 | 20 | ```js 21 | Array.from('xxx'); 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/spec/examples/react/index.module.scss: -------------------------------------------------------------------------------- 1 | @import '~@alifd/next/variables.scss'; 2 | 3 | .PageHeader { 4 | margin-top: -$shell-dark-content-paddingTop; 5 | margin-right: -$shell-dark-content-paddingLeft; 6 | margin-bottom: 0; 7 | margin-left: -$shell-dark-content-paddingLeft; 8 | padding: $shell-dark-content-paddingTop $shell-dark-content-paddingLeft; 9 | background-color: $color-white; 10 | } 11 | 12 | .Title { 13 | display: block; 14 | color: $color-text1-4; 15 | font-weight: $font-weight-3; 16 | font-size: $font-size-title; 17 | line-height: 1.5; 18 | } 19 | 20 | .Description { 21 | display: block; 22 | color: $color-text1-3; 23 | font-size: $font-size-body-2; 24 | line-height: 1.5; 25 | } 26 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig } = require('./packages/spec/src/'); 2 | 3 | // script: eslint --ext .js,.jsx,.tsx,.ts ./ --resolve-plugins-relative-to ./packages/spec 4 | module.exports = getESLintConfig('react', { 5 | rules: { 6 | // For test file. This project is no UI project, not use line height. 7 | '@iceworks/best-practices/recommend-add-line-height-unit': 'off', 8 | }, 9 | parserOptions: { 10 | babelOptions: { 11 | // @babel/preset-react is not a ESLint plugin, resolve-plugins-relative-to is not work 12 | // fix Parsing error: Cannot find module '@babel/preset-react' 13 | // set presets path to ./packages/spec 14 | presets: [require.resolve('@babel/preset-react', { paths: ['./packages/spec'] })], 15 | }, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: 提交一个可复现的缺陷/漏洞 4 | labels: 'bug' 5 | 6 | --- 7 | 8 | 11 | 12 | 包名: 13 | 14 | ## 您的本地环境信息/Your local environment information 15 | 16 | * 操作系统及其版本/System and Version: 17 | * Node version: 18 | * 问题 npm 包 version: 19 | 20 | ## 您遇到的问题及复现步骤/What are your problems and how to reproduce them 21 | 22 | 26 | 27 | ## 您期待的正确结果/The right result you're looking forward to 28 | 29 | 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🚀 Feature Request" 3 | about: 提一个需求 4 | labels: 'enhancement' 5 | 6 | --- 7 | 8 | ### 问题描述/Problem Description 9 | 10 | 14 | 15 | ### 建议的方案/Proposal 16 | 17 | 21 | 22 | ### 备选的方案/Alternatives 23 | 24 | 28 | 29 | ### 附加信息/Other Information 30 | 31 | 35 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/no-js-in-ts-project.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/no-js-in-ts-project'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('no-js-in-ts-project', rule, { 9 | valid: [ 10 | { 11 | filename: 'index.ts', 12 | code: '', 13 | }, 14 | { 15 | filename: '.stylelintrc.js', 16 | code: '', 17 | }, 18 | { 19 | filename: 'home.ts', 20 | code: '', 21 | }, 22 | ], 23 | 24 | invalid: [ 25 | { 26 | filename: 'home.js', 27 | code: '', 28 | errors: [ 29 | { 30 | message: "The 'home.js' is not recommended in TS project", 31 | }, 32 | ], 33 | }, 34 | ], 35 | }); 36 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/docs/rules/recommend-add-line-height-unit.md: -------------------------------------------------------------------------------- 1 | # recommend-add-line-height-unit 2 | 3 | Recommended to add unit for line-height which is more than 5. 4 | 5 | ## Rule Details 6 | 7 | Examples of **incorrect** code for this rule: 8 | 9 | ```js 10 | class App extends React.Component { 11 | render() { 12 | return

hello world

; 13 | } 14 | } 15 | ``` 16 | 17 | ```css 18 | .text { 19 | line-height: 10; 20 | } 21 | ``` 22 | 23 | Should add unit for line-height, like `line-height: 10px;`; 24 | 25 | Examples of **correct** code for this rule: 26 | 27 | ```js 28 | class App extends React.Component { 29 | render() { 30 | return

hello world

; 31 | } 32 | } 33 | ``` 34 | 35 | ```css 36 | .text { 37 | line-height: 2; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iceworks/eslint-plugin-best-practices", 3 | "version": "0.2.11", 4 | "description": "Iceworks best practices eslint plugin.", 5 | "files": [ 6 | "docs/", 7 | "src/" 8 | ], 9 | "keywords": [ 10 | "iceworks", 11 | "best practices", 12 | "eslint", 13 | "eslintplugin", 14 | "eslint-plugin" 15 | ], 16 | "main": "src/index.js", 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "dependencies": { 21 | "@iceworks/spec": "^1.0.0", 22 | "@mdn/browser-compat-data": "^4.0.5", 23 | "fs-extra": "^9.0.1", 24 | "glob": "^7.1.6", 25 | "line-column": "^1.0.2", 26 | "path-to-regexp": "^6.1.0", 27 | "require-all": "^3.0.0", 28 | "semver": "^7.3.2" 29 | }, 30 | "devDependencies": { 31 | "eslint": "^7.22.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/deps-no-resolutions.test.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../../src/rules/deps-no-resolutions'); 2 | const { RuleTester } = require('eslint'); 3 | 4 | const ruleTester = new RuleTester(); 5 | ruleTester.run('deps-no-resolutions', rule, { 6 | valid: [ 7 | { 8 | filename: 'package.json', 9 | code: `module.exports = ${JSON.stringify({ name: 'test' })}`, 10 | }, 11 | { 12 | filename: 'package.js', 13 | code: 'var t = 1', 14 | }, 15 | ], 16 | 17 | invalid: [ 18 | { 19 | filename: 'package.json', 20 | code: `module.exports = ${JSON.stringify({ resolutions: { 'ice.js': '1.0.0' } })}`, 21 | errors: [ 22 | { 23 | message: 'It is not recommended to use resolutions to lock the version', 24 | }, 25 | ], 26 | }, 27 | ], 28 | }); 29 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/recommend-update-rax.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/recommend-update-rax'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('recommend-update-rax', rule, { 9 | valid: [ 10 | { 11 | filename: 'package.json', 12 | code: `module.exports = ${JSON.stringify({ dependencies: { rax: '^1.1.0' } })}`, 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({ dependencies: { rax: '^0.6.0' } })}`, 24 | errors: [ 25 | { 26 | message: 'Rax version < 1.0 , recommend to update Rax', 27 | }, 28 | ], 29 | }, 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/deps-no-ice-scripts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/deps-no-ice-scripts'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('deps-no-ice-scripts', rule, { 9 | valid: [ 10 | { 11 | filename: 'package.json', 12 | code: `module.exports = ${JSON.stringify({ devDependencies: { 'ice.js': '^1.0.0' } })}`, 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({ devDependencies: { 'ice-scripts': '^1.0.0' } })}`, 24 | errors: [ 25 | { 26 | message: 'It is not recommended to use ice-scripts, the new version is ice.js', 27 | }, 28 | ], 29 | }, 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/deps-no-resolutions.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const docsUrl = require('../docsUrl'); 3 | 4 | const RULE_NAME = 'deps-no-resolutions'; 5 | 6 | module.exports = { 7 | name: RULE_NAME, 8 | meta: { 9 | type: 'suggestion', 10 | docs: { 11 | url: docsUrl(RULE_NAME), 12 | }, 13 | fixable: null, 14 | messages: { 15 | depsNoResolutions: 'It is not recommended to use resolutions to lock the version', 16 | }, 17 | }, 18 | 19 | create(context) { 20 | if (path.basename(context.getFilename()) !== 'package.json') { 21 | return {}; 22 | } 23 | return { 24 | Property: function handleRequires(node) { 25 | if (node.key && node.key.value && node.key.value === 'resolutions') { 26 | context.report({ 27 | node, 28 | messageId: 'depsNoResolutions', 29 | }); 30 | } 31 | }, 32 | }; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/no-broad-semantic-versioning.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/no-broad-semantic-versioning'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('no-broad-semantic-versioning', rule, { 9 | valid: [ 10 | { 11 | filename: 'package.json', 12 | code: `module.exports = ${JSON.stringify({ devDependencies: { 'ice.js': '^1.0.0' } })}`, 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({ devDependencies: { 'ice.js': '*' } })}`, 24 | errors: [ 25 | { 26 | message: 'The "ice.js" is not recommended to use "*", and it is recommend using "^1.0.0"', 27 | }, 28 | ], 29 | }, 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/deps-no-router-library.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/deps-no-router-library'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('deps-no-router-library', rule, { 9 | valid: [ 10 | { 11 | filename: 'package.json', 12 | code: `module.exports = ${JSON.stringify({ devDependencies: { 'react-router': '^5.2.0' } })}`, 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: { 'ice.js': '^1.0.0', 'react-router': '^5.2.0' }, 25 | })}`, 26 | errors: [ 27 | { 28 | message: 'It is not recommended to directly rely on routing libraries "react-router"', 29 | }, 30 | ], 31 | }, 32 | ], 33 | }); 34 | -------------------------------------------------------------------------------- /packages/spec/examples/react/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Breadcrumb, Box, Typography } from '@alifd/next'; 3 | import styles from './index.module.scss'; 4 | 5 | const unusedVar = 1; 6 | 7 | const PageHeader = (props) => { 8 | const { breadcrumbs, title, description, ...others } = props; 9 | return ( 10 | 11 | {breadcrumbs && breadcrumbs.length > 0 ? ( 12 | 13 | {breadcrumbs.map((item) => ( 14 | {item.name} 15 | ))} 16 | 17 | ) : null} 18 | 19 | {title && {title}} 20 | 21 | {description && ( 22 | {description} 23 | )} 24 | 25 | ); 26 | }; 27 | 28 | export default PageHeader; 29 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/no-http-url.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/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://test.com';", 12 | }, 13 | ], 14 | 15 | invalid: [ 16 | { 17 | code: "var test = 'http://test.com';", 18 | output: "var test = 'https://test.com';", 19 | errors: [ 20 | { 21 | message: 'Recommended "http://test.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://test.com" switch to HTTPS', 36 | }, 37 | ], 38 | }, 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/no-http-url.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @iceworks/best-practices/no-http-url */ 2 | const docsUrl = require('../docsUrl'); 3 | 4 | const RULE_NAME = 'no-http-url'; 5 | 6 | module.exports = { 7 | name: RULE_NAME, 8 | meta: { 9 | type: 'suggestion', 10 | docs: { 11 | url: docsUrl(RULE_NAME), 12 | }, 13 | fixable: 'code', 14 | messages: { 15 | noHttpUrl: 'Recommended "{{url}}" switch to HTTPS', 16 | }, 17 | }, 18 | create(context) { 19 | return { 20 | Literal: function handleRequires(node) { 21 | if (node.value && typeof node.value === 'string' && node.value.indexOf('http:') === 0) { 22 | context.report({ 23 | node, 24 | messageId: 'noHttpUrl', 25 | data: { 26 | url: node.value, 27 | }, 28 | fix: (fixer) => { 29 | return fixer.replaceText(node, `'${node.value.replace('http:', 'https:')}'`); 30 | }, 31 | }); 32 | } 33 | }, 34 | }; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** 3 | * Scripts to check unpublished version and run publish 4 | */ 5 | const { spawnSync } = require('child_process'); 6 | const getPackageInfos = require('./getPackageInfos'); 7 | 8 | function publish(pkg, version, directory) { 9 | console.log('[PUBLISH]', `${pkg}@${version}`); 10 | 11 | spawnSync( 12 | 'npm', 13 | [ 14 | 'publish', 15 | // use default registry 16 | ], 17 | { 18 | stdio: 'inherit', 19 | cwd: directory, 20 | }, 21 | ); 22 | } 23 | 24 | // Entry 25 | console.log('[PUBLISH] Start:'); 26 | getPackageInfos().then((packageInfos) => { 27 | // Publish 28 | let publishedCount = 0; 29 | for (let i = 0; i < packageInfos.length; i++) { 30 | const { name, directory, localVersion, shouldPublish } = packageInfos[i]; 31 | if (shouldPublish) { 32 | publishedCount++; 33 | console.log(`--- ${name}@${localVersion} ---`); 34 | publish(name, localVersion, directory); 35 | } 36 | } 37 | console.log(`[PUBLISH PACKAGE PRODUCTION] Complete (count=${publishedCount}):`); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iceworks-spec", 3 | "private": true, 4 | "devEngines": { 5 | "node": "8.x || 9.x || 10.x || 11.x", 6 | "npm": "6.x" 7 | }, 8 | "devDependencies": { 9 | "@commitlint/cli": "^11.0.0", 10 | "codecov": "^3.6.1", 11 | "commitlint-config-ali": "^0.1.0", 12 | "eslint": "^7.22.0", 13 | "husky": "^7.0.1", 14 | "ice-npm-utils": "^2.0.1", 15 | "jest": "^24.9.0", 16 | "lerna": "^3.18.2", 17 | "prettier": "^2.1.0", 18 | "stylelint": "^13.2.0", 19 | "typescript": "^3.5.3" 20 | }, 21 | "scripts": { 22 | "prepare": "husky install", 23 | "setup": "rm -rf ./packages/*/node_modules && lerna bootstrap --no-ci && lerna link", 24 | "lint": "eslint --ext .js,.jsx,.tsx,.ts ./ --resolve-plugins-relative-to ./packages/spec", 25 | "prettier": "prettier **/* --write", 26 | "test": "jest && cd packages/spec && npm run test", 27 | "publish": "node ./scripts/publish", 28 | "ci": "npm run setup && npm run lint && npm run test" 29 | }, 30 | "jest": { 31 | "coverageDirectory": "./coverage/", 32 | "collectCoverage": true 33 | } 34 | } -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/recommend-functional-component.test.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../../src/rules/recommend-functional-component'); 2 | const { RuleTester } = require('eslint'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('recommend-functional-component', rule, { 7 | valid: [ 8 | { 9 | code: ` 10 | const App = () => { 11 | return (

hello world

); 12 | }; 13 | `, 14 | parserOptions: { 15 | ecmaVersion: 6, 16 | sourceType: 'module', 17 | ecmaFeatures: { 18 | jsx: true, 19 | }, 20 | }, 21 | }, 22 | ], 23 | 24 | invalid: [ 25 | { 26 | code: ` 27 | class App extends React.Component { 28 | render() { 29 | return (

hello world

); 30 | } 31 | }; 32 | `, 33 | parserOptions: { 34 | ecmaVersion: 6, 35 | sourceType: 'module', 36 | ecmaFeatures: { 37 | jsx: true, 38 | }, 39 | }, 40 | errors: [ 41 | { 42 | message: "It is not recommended to use class component 'App'", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }); 48 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/recommend-add-line-height-unit.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/recommend-add-line-height-unit'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('recommend-add-line-height-unit', rule, { 9 | valid: [ 10 | { 11 | filename: 'index.js', 12 | code: 'var style = { position: "relative", lineHeight: "30px" };', 13 | }, 14 | ], 15 | 16 | invalid: [ 17 | { 18 | filename: 'index.js', 19 | code: 'var style = { position: "relative", lineHeight: 30 };', 20 | errors: [ 21 | { 22 | message: 'Please add unit (like px, rpx ...) for "lineHeight: 30 " in "index.js".', 23 | }, 24 | ], 25 | }, 26 | { 27 | filename: 'index.jsx', 28 | code: '

hello world

', 29 | parserOptions: { 30 | ecmaFeatures: { 31 | jsx: true, 32 | }, 33 | }, 34 | errors: [ 35 | { 36 | message: 'Please add unit (like px, rpx ...) for "lineHeight: 10 " in "index.jsx".', 37 | }, 38 | ], 39 | }, 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /packages/spec/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # changelog 2 | 3 | ## 1.6.0 4 | 5 | - Feat update peerDependencies ESLint version to `>=7.5.0`. 6 | 7 | ## 1.5.0 8 | 9 | - Feat update eslint-config-ali, babel and typescript dependencies to support ESLint@8.x. 10 | - Update stylistic rules `dot-notation` and `@typescript-eslint/dot-notation` from error to off. 11 | 12 | ## 1.4.2 13 | 14 | - Fix stylelint extends config merge error. 15 | 16 | ## 1.4.1 17 | 18 | - Fix build.json targets config not exist error. 19 | 20 | ## 1.4.0 21 | 22 | - Update stylistic rules `semi` `eol-last` and `quote-props` from error to warn. 23 | - Support auto add specific rules ([eslint-plugin-rax-compile-time-miniapp](https://www.npmjs.com/package/eslint-plugin-rax-compile-time-miniapp)) for rax compile-time miniapp. 24 | 25 | ## 1.3.2 26 | 27 | - Add [eslint-plugin-jsx-plus](https://github.com/jsx-plus/eslint-plugin-jsx-plus) support Rax JSX+. 28 | 29 | ## 1.3.1 30 | 31 | - Add stylelint peerDependencies 32 | 33 | ## 1.3.0 34 | 35 | - Update ESLint from 6.x to 7.x 36 | - Update eslint-config-ali from 11.x to 12.x 37 | 38 | ## 1.2.0 39 | 40 | - ESlint config support `common` and `common-ts` rules. 41 | - Commitlint, prettier, stylelint config support `common` rules. 42 | 43 | ## 1.1.0 44 | 45 | - Support `vue` and `vue-ts` project. 46 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/recommend-functional-component.js: -------------------------------------------------------------------------------- 1 | const docsUrl = require('../docsUrl'); 2 | 3 | const RULE_NAME = 'recommend-functional-component'; 4 | 5 | module.exports = { 6 | name: RULE_NAME, 7 | meta: { 8 | docs: { 9 | url: docsUrl(RULE_NAME), 10 | }, 11 | fixable: null, 12 | messages: { 13 | // eslint-disable-next-line 14 | recommendFunctionalComponent: "It is not recommended to use class component '{{name}}'", 15 | }, 16 | }, 17 | 18 | create(context) { 19 | return { 20 | ClassDeclaration: function handleRequires(node) { 21 | const { name } = node.id || {}; 22 | let superName = ''; 23 | if (node.superClass) { 24 | if (node.superClass.name) { 25 | // class xxx extends Component 26 | superName = node.superClass.name; 27 | } else if (node.superClass.property && node.superClass.property.name) { 28 | // class xxx extends React.Component 29 | superName = node.superClass.property.name; 30 | } 31 | } 32 | 33 | // Class Component 34 | if (superName === 'Component') { 35 | context.report({ 36 | node, 37 | messageId: 'recommendFunctionalComponent', 38 | data: { 39 | name: name || 'Anonymous', 40 | }, 41 | }); 42 | } 43 | }, 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/deps-no-ice-scripts.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const docsUrl = require('../docsUrl'); 3 | 4 | const RULE_NAME = 'deps-no-ice-scripts'; 5 | 6 | module.exports = { 7 | name: RULE_NAME, 8 | meta: { 9 | type: 'suggestion', 10 | docs: { 11 | url: docsUrl(RULE_NAME), 12 | }, 13 | fixable: 'code', 14 | messages: { 15 | depsNoResolutions: 'It is not recommended to use ice-scripts, the new version is ice.js', 16 | }, 17 | }, 18 | 19 | create(context) { 20 | if (path.basename(context.getFilename()) !== 'package.json') { 21 | return {}; 22 | } 23 | 24 | return { 25 | Property: function handleRequires(node) { 26 | if ( 27 | node.key && 28 | node.key.value && 29 | (node.key.value === 'dependencies' || node.key.value === 'devDependencies') && 30 | node.value && 31 | node.value.properties 32 | ) { 33 | node.value.properties.forEach((property) => { 34 | if (property.key && property.key.value) { 35 | const dependencyName = property.key.value; 36 | 37 | if (dependencyName === 'ice-scripts') { 38 | context.report({ 39 | loc: property.loc, 40 | messageId: 'depsNoResolutions', 41 | }); 42 | } 43 | } 44 | }); 45 | } 46 | }, 47 | }; 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/no-js-in-ts-project.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const docsUrl = require('../docsUrl'); 3 | 4 | const RULE_NAME = 'no-js-in-ts-project'; 5 | 6 | const TS_REG = /\.tsx?$/; 7 | const JS_REG = /\.jsx?$/; 8 | 9 | const WHITE_LIST = ['commitlint.config.js', 'eslintrc.js', 'prettierrc.js', 'stylelintrc.js']; 10 | 11 | const WHITE_LIST_REG = new RegExp(`(${WHITE_LIST.join('|')})$`); 12 | 13 | let isTSProject = false; 14 | 15 | module.exports = { 16 | name: RULE_NAME, 17 | meta: { 18 | type: 'suggestion', 19 | docs: { 20 | url: docsUrl(RULE_NAME), 21 | }, 22 | fixable: null, 23 | messages: { 24 | noJSInTSProject: "The '{{fileName}}' is not recommended in TS project", 25 | }, 26 | }, 27 | 28 | create(context) { 29 | const fileName = context.getFilename(); 30 | const extName = path.extname(fileName); 31 | if (TS_REG.test(extName)) { 32 | isTSProject = true; 33 | } 34 | 35 | if (isTSProject && !WHITE_LIST_REG.test(fileName) && JS_REG.test(extName)) { 36 | context.report({ 37 | loc: { 38 | start: { 39 | line: 0, 40 | column: 0, 41 | }, 42 | end: { 43 | line: 0, 44 | column: 0, 45 | }, 46 | }, 47 | messageId: 'noJSInTSProject', 48 | data: { 49 | fileName, 50 | }, 51 | }); 52 | } 53 | 54 | // Necessary 55 | return {}; 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/recommend-update-rax.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const semver = require('semver'); 3 | const docsUrl = require('../docsUrl'); 4 | 5 | const RULE_NAME = 'recommend-update-rax'; 6 | 7 | module.exports = { 8 | name: RULE_NAME, 9 | meta: { 10 | type: 'suggestion', 11 | docs: { 12 | url: docsUrl(RULE_NAME), 13 | }, 14 | fixable: null, 15 | messages: { 16 | depsRecommendUpdateRax: 'Rax version < 1.0 , recommend to update Rax', 17 | }, 18 | }, 19 | 20 | create(context) { 21 | if (path.basename(context.getFilename()) !== 'package.json') { 22 | return {}; 23 | } 24 | 25 | return { 26 | Property: function handleRequires(node) { 27 | if ( 28 | node.key && 29 | node.key.value && 30 | (node.key.value === 'dependencies' || node.key.value === 'devDependencies') && 31 | node.value && 32 | node.value.properties 33 | ) { 34 | node.value.properties.forEach((property) => { 35 | if (property.key && property.key.value) { 36 | const dependencyName = property.key.value; 37 | 38 | if (dependencyName === 'rax' && semver.satisfies('0.6.7', property.value.value)) { 39 | context.report({ 40 | loc: property.loc, 41 | messageId: 'depsRecommendUpdateRax', 42 | }); 43 | } 44 | } 45 | }); 46 | } 47 | }, 48 | }; 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /packages/spec/src/getRaxEslintConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const JSON5 = require('json5'); 4 | const deepmerge = require('./deepmerge'); 5 | 6 | // Add specific rules for rax compile-time miniapp 7 | module.exports = function (config) { 8 | try { 9 | const buildConfigFilePath = path.join(process.cwd(), 'build.json'); 10 | 11 | if (fs.existsSync(buildConfigFilePath)) { 12 | 13 | const buildConfig = JSON5.parse(fs.readFileSync(buildConfigFilePath, 'utf8')); 14 | 15 | const isCompileTime = (target) => ( 16 | buildConfig.targets && buildConfig.targets.find((t) => t === target) && 17 | buildConfig[target] && buildConfig[target].buildType === 'compile' 18 | ); 19 | 20 | // At present, only miniapp and wechat-miniprogram support build for compile-time 21 | if (isCompileTime('miniapp') || isCompileTime('wechat-miniprogram')) { 22 | // https://www.npmjs.com/package/eslint-plugin-rax-compile-time-miniapp 23 | const { configs } = require('eslint-plugin-rax-compile-time-miniapp'); 24 | 25 | config.plugins = config.plugins || []; 26 | 27 | config.plugins.push('rax-compile-time-miniapp'); 28 | // For some ci and jest test env, we chose set config instead 'plugin:rax-compile-time-miniapp/recommended' 29 | config.rules = deepmerge(config.rules || {}, configs.recommended.rules); 30 | } 31 | } 32 | } catch (e) { 33 | console.log('Add specific rules for rax compile-time miniapp failed!', e); 34 | } 35 | return config; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/no-lowercase-component-name.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../src/rules/no-lowercase-component-name'); 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester(); 7 | 8 | ruleTester.run('no-lowercase-component-name', rule, { 9 | valid: [ 10 | { 11 | filename: 'pages/App/index.jsx', 12 | code: ` 13 | const App = () => { 14 | return (

hello world

); 15 | }; 16 | 17 | export default App; 18 | `, 19 | parserOptions: { 20 | ecmaVersion: 6, 21 | sourceType: 'module', 22 | ecmaFeatures: { 23 | jsx: true, 24 | }, 25 | }, 26 | }, 27 | ], 28 | 29 | invalid: [ 30 | { 31 | filename: 'src/components/app/index.jsx', 32 | code: '', 33 | errors: [ 34 | { 35 | message: "It is not recommended to name components in lower case 'app'", 36 | }, 37 | ], 38 | }, 39 | { 40 | filename: 'src/pages/App/index.jsx', 41 | code: ` 42 | const app = () => { 43 | return (

hello world

); 44 | }; 45 | 46 | export default app; 47 | `, 48 | parserOptions: { 49 | ecmaVersion: 6, 50 | sourceType: 'module', 51 | ecmaFeatures: { 52 | jsx: true, 53 | }, 54 | }, 55 | errors: [ 56 | { 57 | message: "It is not recommended to name components in lower case 'app'", 58 | }, 59 | ], 60 | }, 61 | ], 62 | }); 63 | -------------------------------------------------------------------------------- /packages/spec/src/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const deepmerge = require('./deepmerge'); 3 | const requireAll = require('require-all'); 4 | 5 | const eslint = requireAll({ 6 | dirname: path.resolve(__dirname, 'eslint'), 7 | }); 8 | const stylelint = requireAll({ 9 | dirname: path.resolve(__dirname, 'stylelint'), 10 | }); 11 | const prettier = requireAll({ 12 | dirname: path.resolve(__dirname, 'prettier'), 13 | }); 14 | const commitlint = requireAll({ 15 | dirname: path.resolve(__dirname, 'commitlint'), 16 | }); 17 | 18 | function getConfig(configs, rule, customConfig) { 19 | if (!configs[rule]) { 20 | throw new Error(`Rule '${rule}' not Support!`); 21 | } 22 | 23 | return deepmerge(configs[rule], customConfig || {}); 24 | } 25 | 26 | // ESLint 27 | exports.getESLintConfig = function (rule, customConfig) { 28 | return getConfig(eslint, rule, customConfig); 29 | }; 30 | 31 | // stylelint 32 | exports.getStylelintConfig = function (rule, customConfig) { 33 | return getConfig(stylelint, rule, customConfig); 34 | }; 35 | 36 | // prettier 37 | exports.getPrettierConfig = function (rule, customConfig) { 38 | return getConfig(prettier, rule, customConfig); 39 | }; 40 | 41 | // commitlint 42 | exports.getCommitlintConfig = function (rule, customConfig) { 43 | return getConfig(commitlint, rule, customConfig); 44 | }; 45 | 46 | // deepmerge for lint rules 47 | // Such as deepmerge({ rules: { 'comma-dangle': [1, 'never'] } }, { rules: { 'comma-dangle': [2, 'always'] } }) 48 | // Get { rules: { 'comma-dangle': [2, 'always'] } Not { rules: { 'comma-dangle': [1, 'never', 2, 'always'] } 49 | exports.deepmerge = deepmerge; 50 | -------------------------------------------------------------------------------- /packages/spec/examples/react-ts/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | } 4 | 5 | .actionBar { 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | margin-bottom: 20px; 10 | } 11 | 12 | .buttonGroup { 13 | :global { 14 | .next-btn { 15 | margin-right: 12px; 16 | } 17 | } 18 | } 19 | 20 | .rightButtonGroup { 21 | :global { 22 | .next-btn { 23 | margin-left: 12px; 24 | } 25 | } 26 | } 27 | 28 | .columnSortPanel { 29 | :global { 30 | background: #fff; 31 | padding: 20px; 32 | border-radius: $corner-1; 33 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.5); 34 | max-height: 500px; 35 | overflow-y: scroll; 36 | width: 180px; 37 | 38 | .sort-item-container { 39 | width: 100%; 40 | } 41 | 42 | .sort-item { 43 | display: flex; 44 | align-items: center; 45 | justify-content: space-between; 46 | margin-bottom: 11px; 47 | 48 | .next-checkbox-wrapper.sort-checkbox { 49 | white-space: nowrap; 50 | max-width: calc(100% - 16px - 20px); 51 | 52 | .next-checkbox-label { 53 | white-space: nowrap; 54 | text-overflow: ellipsis; 55 | overflow: hidden; 56 | } 57 | } 58 | 59 | .column-handle { 60 | display: block; 61 | visibility: hidden; 62 | margin-left: 20px; 63 | } 64 | 65 | &:hover { 66 | .column-handle { 67 | visibility: initial; 68 | cursor: move; 69 | } 70 | } 71 | } 72 | 73 | .sort-item-children { 74 | margin-left: 18px; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/configs/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['@iceworks/best-practices'], 3 | overrides: [ 4 | { 5 | files: ['package.json'], 6 | processor: '@iceworks/best-practices/.json', 7 | rules: { 8 | quotes: 'off', 9 | semi: 'off', 10 | 'eol-last': 'off', 11 | 'quote-props': 'off', 12 | 'comma-dangle': 'off', 13 | '@iceworks/best-practices/deps-no-ice-scripts': 'warn', 14 | '@iceworks/best-practices/deps-no-resolutions': 'warn', 15 | '@iceworks/best-practices/deps-no-router-library': 'warn', 16 | '@iceworks/best-practices/no-broad-semantic-versioning': 'error', 17 | '@iceworks/best-practices/recommend-update-rax': 'warn', 18 | }, 19 | }, 20 | { 21 | files: [ 22 | '**/__tests__/*.{j,t}s?(x)', 23 | '**/tests/*.{j,t}s?(x)', 24 | '**/test/*.{j,t}s?(x)', 25 | ], 26 | env: { jest: true }, 27 | }, 28 | ], 29 | rules: { 30 | 'dot-notation': 'off', 31 | 'max-len': ['warn', 120, 2, { 32 | ignoreUrls: true, 33 | ignoreComments: false, 34 | ignoreRegExpLiterals: true, 35 | ignoreStrings: true, 36 | ignoreTemplateLiterals: true, 37 | }], 38 | 'semi': 'warn', 39 | 'eol-last': 'warn', 40 | 'quote-props': 'warn', 41 | '@iceworks/best-practices/no-http-url': 'warn', 42 | '@iceworks/best-practices/no-js-in-ts-project': 'warn', 43 | '@iceworks/best-practices/no-lowercase-component-name': 'warn', 44 | '@iceworks/best-practices/recommend-add-line-height-unit': 'error', 45 | '@iceworks/best-practices/recommend-functional-component': 'warn', 46 | '@iceworks/best-practices/recommend-polyfill': 'warn', 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /packages/spec/test/index.test.js: -------------------------------------------------------------------------------- 1 | const { getESLintConfig, getStylelintConfig, getPrettierConfig } = require('../src'); 2 | 3 | it('getESLintConfig set one rule should be replaced', () => { 4 | const result = getESLintConfig('react', { 5 | rules: { 6 | 'comma-dangle': [1, 'never'], 7 | }, 8 | }); 9 | 10 | expect(result.rules['comma-dangle']).toEqual([1, 'never']); 11 | }); 12 | 13 | it('getESLintConfig parserOptions should be merged', () => { 14 | const result = getESLintConfig('rax', { 15 | parserOptions: { 16 | ecmaVersion: 2017, 17 | ecmaFeatures: { 18 | js: true, 19 | }, 20 | }, 21 | }); 22 | 23 | expect(result.parserOptions.ecmaVersion).toEqual(2017); 24 | expect(result.parserOptions.ecmaFeatures).toEqual({ 25 | js: true, 26 | }); 27 | expect(result.parserOptions.ecmaFeatures.jsx).toEqual(undefined); 28 | }); 29 | 30 | it('getStylelintConfig rules should be merged', () => { 31 | const result = getStylelintConfig('react', { 32 | rules: { 33 | 'block-no-empty': null, 34 | }, 35 | }); 36 | 37 | expect(result.rules['block-no-empty']).toEqual(null); 38 | }); 39 | 40 | it('getPrettierConfig tabWidth should be replaced', () => { 41 | const result = getPrettierConfig('react', { 42 | tabWidth: 4, 43 | }); 44 | 45 | expect(result.tabWidth).toEqual(4); 46 | }); 47 | 48 | it('new attributes should be merged', () => { 49 | const result = getESLintConfig('react', { 50 | newAttribute1: ['xxxx-val1'], 51 | newAttribute2: { a: 'xxxx-val2' }, 52 | newAttribute3: 'xxxx-val3', 53 | }); 54 | 55 | expect(result.newAttribute1[0]).toEqual('xxxx-val1'); 56 | expect(result.newAttribute2.a).toEqual('xxxx-val2'); 57 | expect(result.newAttribute3).toEqual('xxxx-val3'); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/deps-no-router-library.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const docsUrl = require('../docsUrl'); 3 | 4 | const RULE_NAME = 'deps-no-router-library'; 5 | 6 | module.exports = { 7 | name: RULE_NAME, 8 | meta: { 9 | type: 'suggestion', 10 | docs: { 11 | url: docsUrl(RULE_NAME), 12 | }, 13 | fixable: null, 14 | messages: { 15 | depsNoRouterLibrary: 'It is not recommended to directly rely on routing libraries "{{name}}"', 16 | }, 17 | }, 18 | 19 | create(context) { 20 | const sourceCode = context.getSourceCode(); 21 | const sourceCodeText = sourceCode.getText(); 22 | if ( 23 | path.basename(context.getFilename()) !== 'package.json' || 24 | (sourceCodeText.indexOf('ice.js') === -1 && sourceCodeText.indexOf('rax-app') === -1) 25 | ) { 26 | return {}; 27 | } 28 | 29 | const routerLibs = ['react-router', 'vue-router', 'rax-use-router']; 30 | 31 | return { 32 | Property: function handleRequires(node) { 33 | if ( 34 | node.key && 35 | node.key.value && 36 | (node.key.value === 'dependencies' || node.key.value === 'devDependencies') && 37 | node.value && 38 | node.value.properties 39 | ) { 40 | node.value.properties.forEach((property) => { 41 | if (property.key && property.key.value) { 42 | const dependencyName = property.key.value; 43 | const routerLib = routerLibs.find((lib) => dependencyName === lib); 44 | 45 | if (routerLib) { 46 | context.report({ 47 | loc: property.loc, 48 | messageId: 'depsNoRouterLibrary', 49 | data: { 50 | name: routerLib, 51 | }, 52 | }); 53 | } 54 | } 55 | }); 56 | } 57 | }, 58 | }; 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/no-lowercase-component-name.js: -------------------------------------------------------------------------------- 1 | const { pathToRegexp } = require('path-to-regexp'); 2 | const docsUrl = require('../docsUrl'); 3 | 4 | const RULE_NAME = 'no-lowercase-component-name'; 5 | 6 | module.exports = { 7 | name: RULE_NAME, 8 | meta: { 9 | type: 'suggestion', 10 | docs: { 11 | url: docsUrl(RULE_NAME), 12 | }, 13 | fixable: null, 14 | messages: { 15 | noLowerCaseComponentName: "It is not recommended to name components in lower case '{{name}}'", 16 | }, 17 | }, 18 | 19 | create(context) { 20 | const COMPONENT_REG = pathToRegexp('components/:name/index.(js|jsx)', [], { start: false }); 21 | const PAGE_REG = pathToRegexp('pages/:name/index.(js|jsx)', [], { start: false }); 22 | 23 | const fileName = context.getFilename(); 24 | 25 | // component or page name (jsx component) 26 | const name = 27 | (COMPONENT_REG.exec(fileName) && COMPONENT_REG.exec(fileName)[1]) || 28 | (PAGE_REG.exec(fileName) && PAGE_REG.exec(fileName)[1]); 29 | 30 | // https://github.com/airbnb/javascript/tree/master/react#naming 31 | // Check filename 32 | if (name && name[0].toUpperCase() !== name[0]) { 33 | context.report({ 34 | loc: { 35 | start: { 36 | line: 0, 37 | column: 0, 38 | }, 39 | end: { 40 | line: 0, 41 | column: 0, 42 | }, 43 | }, 44 | messageId: 'noLowerCaseComponentName', 45 | data: { 46 | name, 47 | }, 48 | }); 49 | } 50 | 51 | return { 52 | // Check export componet name 53 | ExportDefaultDeclaration: function handleRequires(node) { 54 | if ( 55 | name && 56 | node.declaration && 57 | node.declaration.name && 58 | node.declaration.name[0].toUpperCase() !== node.declaration.name[0] 59 | ) { 60 | context.report({ 61 | node, 62 | messageId: 'noLowerCaseComponentName', 63 | data: { 64 | name: node.declaration.name, 65 | }, 66 | }); 67 | } 68 | }, 69 | }; 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /scripts/getPackageInfos.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { existsSync, readdirSync, readFileSync } = require('fs'); 3 | const { join } = require('path'); 4 | const { getLatestVersion } = require('ice-npm-utils'); 5 | 6 | const TARGET_DIRECTORY = join(__dirname, '../packages'); 7 | 8 | function checkBuildSuccess(directory, mainFile) { 9 | return existsSync(join(directory, mainFile)); 10 | } 11 | 12 | function checkVersionExists(pkg, version) { 13 | return getLatestVersion(pkg) 14 | .then((latestVersion) => version === latestVersion) 15 | .catch(() => false); 16 | } 17 | 18 | module.exports = async function getPackageInfos() { 19 | const packageInfos = []; 20 | if (!existsSync(TARGET_DIRECTORY)) { 21 | console.log(`[ERROR] Directory ${TARGET_DIRECTORY} not exist!`); 22 | } else { 23 | const packageFolders = readdirSync(TARGET_DIRECTORY).filter((filename) => filename[0] !== '.'); 24 | console.log('[PUBLISH] Start check with following packages:'); 25 | await Promise.all( 26 | packageFolders.map(async (packageFolder) => { 27 | const directory = join(TARGET_DIRECTORY, packageFolder); 28 | const packageInfoPath = join(directory, 'package.json'); 29 | 30 | // Process package info. 31 | if (existsSync(packageInfoPath)) { 32 | const packageInfo = JSON.parse(readFileSync(packageInfoPath, 'utf8')); 33 | const packageName = packageInfo.name || packageFolder; 34 | 35 | console.log(`- ${packageName}`); 36 | 37 | try { 38 | packageInfos.push({ 39 | name: packageName, 40 | directory, 41 | localVersion: packageInfo.version, 42 | mainFile: packageInfo.main, 43 | // If localVersion not exist, publish it 44 | shouldPublish: 45 | checkBuildSuccess(directory, packageInfo.main) && 46 | !(await checkVersionExists(packageName, packageInfo.version)), 47 | }); 48 | } catch (e) { 49 | console.log(`[ERROR] get ${packageName} information failed: `, e); 50 | } 51 | } else { 52 | console.log(`[ERROR] ${packageFolder}'s package.json not found.`); 53 | } 54 | }), 55 | ); 56 | } 57 | return packageInfos; 58 | }; 59 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/test/src/rules/recommend-polyfill.test.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../../src/rules/recommend-polyfill'); 2 | const { RuleTester } = require('eslint'); 3 | 4 | const ruleTester = new RuleTester(); 5 | 6 | ruleTester.run('recommend-polyfill', rule, { 7 | valid: [ 8 | { 9 | code: 'Array.from("xxxx");', 10 | }, 11 | ], 12 | 13 | invalid: [ 14 | { 15 | code: 'navigator.sendBeacon("xxx");', 16 | errors: [ 17 | { 18 | message: 19 | '[Critical] It is recommended to add polyfill for "navigator.sendBeacon", This might be caused by a compatibility problem in "safari@9"', 20 | }, 21 | ], 22 | }, 23 | { 24 | code: '[0, 1, 2, [3, 4]].flat(2)', 25 | errors: [ 26 | { 27 | message: 28 | '[Critical] It is recommended to add polyfill for "Array.flat", This might be caused by a compatibility problem in "safari@9"', 29 | }, 30 | ], 31 | }, 32 | { 33 | code: 'Array.prototype.flat.apply([0, 1, 2, [3, 4]], 2)', 34 | errors: [ 35 | { 36 | message: 37 | '[Critical] It is recommended to add polyfill for "Array.flat", This might be caused by a compatibility problem in "safari@9"', 38 | }, 39 | ], 40 | }, 41 | { 42 | code: 'new Proxy({}, function() {});', 43 | errors: [ 44 | { 45 | message: 46 | '[Critical] It is recommended to add polyfill for "Proxy", This might be caused by a compatibility problem in "safari@9"', 47 | }, 48 | ], 49 | }, 50 | { 51 | code: 'Reflect.apply(Math.floor, undefined, [1.75]);', 52 | errors: [ 53 | { 54 | message: 55 | '[Critical] It is recommended to add polyfill for "Reflect.apply", This might be caused by a compatibility problem in "safari@9"', 56 | }, 57 | ], 58 | }, 59 | { 60 | code: ` 61 | var p = new Promise(function() {}); 62 | p.finally(function() {}); 63 | `, 64 | errors: [ 65 | { 66 | message: 67 | '[Critical] It is recommended to add polyfill for "Promise.finally", This might be caused by a compatibility problem in "iOS9"', 68 | }, 69 | ], 70 | }, 71 | ], 72 | }); 73 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/recommend-add-line-height-unit.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const lineColumn = require('line-column'); 4 | const docsUrl = require('../docsUrl'); 5 | const { glob } = require('glob'); 6 | 7 | const RULE_NAME = 'recommend-add-line-height-unit'; 8 | 9 | const CSS_REG = /line-height:([\s\d]+);/g; 10 | const JSX_REG = /lineHeight:([\s\d]+)/g; 11 | 12 | const FILE_CACHE = {}; 13 | 14 | const getCheckAndReportFn = (context) => { 15 | return (sourceCodeText, fileType, basename) => { 16 | let matched; 17 | const reg = fileType === 'jsx' ? JSX_REG : CSS_REG; 18 | // eslint-disable-next-line 19 | while ((matched = reg.exec(sourceCodeText)) !== null) { 20 | const target = matched[0]; 21 | const value = matched[1]; 22 | 23 | // line height > 5 need add unit 24 | if (Number(value) > 5) { 25 | const startPosition = lineColumn(sourceCodeText).fromIndex(matched.index); 26 | 27 | context.report({ 28 | loc: { 29 | start: { line: startPosition.line, column: startPosition.col }, 30 | end: { line: startPosition.line, column: startPosition.col + target.length }, 31 | }, 32 | messageId: 'recommendAddLineHeightUnit', 33 | data: { target, basename }, 34 | }); 35 | } 36 | } 37 | }; 38 | }; 39 | 40 | module.exports = { 41 | meta: { 42 | type: 'problem', 43 | docs: { 44 | url: docsUrl(RULE_NAME), 45 | }, 46 | fixable: null, 47 | messages: { 48 | // eslint-disable-next-line 49 | recommendAddLineHeightUnit: 'Please add unit (like px, rpx ...) for "{{target}}" in "{{basename}}".', 50 | }, 51 | }, 52 | 53 | create(context) { 54 | const checkAndReport = getCheckAndReportFn(context); 55 | 56 | const fileName = context.getFilename(); 57 | const dirname = path.dirname(fileName); 58 | 59 | const sourceCode = context.getSourceCode(); 60 | const sourceCodeText = sourceCode.getText(); 61 | 62 | checkAndReport(sourceCodeText, 'jsx', path.basename(fileName)); 63 | 64 | glob.sync(`${dirname}/*.{css,scss,less}`).forEach((cssFileName) => { 65 | if (!FILE_CACHE[cssFileName]) { 66 | FILE_CACHE[cssFileName] = true; 67 | checkAndReport(fs.readFileSync(cssFileName, 'utf-8'), 'css', path.basename(cssFileName)); 68 | } 69 | }); 70 | 71 | // Necessary 72 | return {}; 73 | }, 74 | }; 75 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/no-broad-semantic-versioning.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | const docsUrl = require('../docsUrl'); 4 | 5 | const RULE_NAME = 'no-broad-semantic-versioning'; 6 | 7 | module.exports = { 8 | name: RULE_NAME, 9 | meta: { 10 | type: 'problem', 11 | docs: { 12 | url: docsUrl(RULE_NAME), 13 | }, 14 | fixable: 'code', 15 | messages: { 16 | noBroadSemanticVersioning: 17 | 'The "{{dependencyName}}" is not recommended to use "{{versioning}}", and it is recommend using "{{newVersioning}}"', 18 | }, 19 | }, 20 | 21 | create(context) { 22 | if (path.basename(context.getFilename()) !== 'package.json') { 23 | return {}; 24 | } 25 | 26 | const cwd = context.getCwd(); 27 | 28 | return { 29 | Property: function handleRequires(node) { 30 | if ( 31 | node.key && 32 | node.key.value && 33 | (node.key.value === 'dependencies' || node.key.value === 'devDependencies') && 34 | node.value && 35 | node.value.properties 36 | ) { 37 | node.value.properties.forEach((property) => { 38 | if (property.key && property.key.value) { 39 | const dependencyName = property.key.value; 40 | const dependencyVersion = property.value.value; 41 | if ( 42 | // * 43 | dependencyVersion.indexOf('*') > -1 || 44 | // x.x 45 | dependencyVersion.indexOf('x') > -1 || 46 | // > x 47 | dependencyVersion.indexOf('>') > -1 48 | ) { 49 | let newVersioning = '^1.0.0'; 50 | const dependencyPackageFile = path.join( 51 | cwd, 52 | 'node_modules', 53 | dependencyName, 54 | 'package.json', 55 | ); 56 | if (fs.existsSync(dependencyPackageFile)) { 57 | const dependencyPackage = fs.readJSONSync(dependencyPackageFile); 58 | newVersioning = `^${dependencyPackage.version}`; 59 | } 60 | 61 | context.report({ 62 | loc: property.loc, 63 | messageId: 'noBroadSemanticVersioning', 64 | data: { 65 | dependencyName, 66 | versioning: dependencyVersion, 67 | newVersioning, 68 | }, 69 | }); 70 | } 71 | } 72 | }); 73 | } 74 | }, 75 | }; 76 | }, 77 | }; 78 | -------------------------------------------------------------------------------- /packages/spec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iceworks/spec", 3 | "version": "1.6.0", 4 | "description": "Easy to use eslint/stylelint/prettier/commitlint in rax, ice and react project.", 5 | "main": "src/index.js", 6 | "files": [ 7 | "src" 8 | ], 9 | "scripts": { 10 | "eslint-common": "eslint --no-eslintrc -c .eslintrc.common.js --ext .js,.jsx ./examples/common/", 11 | "eslint-common-ts": "eslint --no-eslintrc -c .eslintrc.common-ts.js --ext .ts,.tsx ./examples/common-ts/", 12 | "eslint-rax": "eslint --no-eslintrc -c .eslintrc.rax.js --ext .js,.jsx ./examples/rax/", 13 | "eslint-rax-ts": "eslint --no-eslintrc -c .eslintrc.rax-ts.js --ext .ts,.tsx ./examples/rax-ts/", 14 | "eslint-react": "eslint --no-eslintrc -c .eslintrc.react.js --ext .js,.jsx ./examples/react/", 15 | "eslint-react-ts": "eslint --no-eslintrc -c .eslintrc.react-ts.js --ext .ts,.tsx ./examples/react-ts/", 16 | "eslint-vue": "eslint --no-eslintrc -c .eslintrc.vue.js --ext .vue ./examples/vue/", 17 | "eslint-vue-ts": "eslint --no-eslintrc -c .eslintrc.vue-ts.js --ext .vue ./examples/vue-ts/", 18 | "eslint-test": "npm run eslint-common && npm run eslint-common-ts && npm run eslint-rax && npm run eslint-rax-ts && npm run eslint-react && npm run eslint-react-ts && npm run eslint-vue && npm run eslint-vue-ts", 19 | "stylelin-test": "stylelint ./**/*.{css,scss,vue}", 20 | "test": "npm run eslint-test && npm run stylelin-test", 21 | "prepublishOnly": "npm run test" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ice-lab/spec.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/ice-lab/spec/issues" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | }, 33 | "homepage": "https://github.com/ice-lab/spec#readme", 34 | "peerDependencies": { 35 | "eslint": ">=7.5.0", 36 | "stylelint": ">=8.3.0" 37 | }, 38 | "dependencies": { 39 | "@iceworks/eslint-plugin-best-practices": "^0.2.0", 40 | "@typescript-eslint/eslint-plugin": "^5.0.0", 41 | "@typescript-eslint/parser": "^5.0.0", 42 | "@babel/core": "^7.16.0", 43 | "@babel/eslint-parser": "^7.16.3", 44 | "@babel/preset-react": "^7.16.0", 45 | "commitlint-config-ali": "^0.1.0", 46 | "eslint-config-ali": "^13.0.0", 47 | "eslint-plugin-import": "^2.22.1", 48 | "eslint-plugin-jsx-plus": "^0.1.0", 49 | "eslint-plugin-rax-compile-time-miniapp": "^1.0.0", 50 | "eslint-plugin-react": "^7.21.5", 51 | "eslint-plugin-react-hooks": "^4.2.0", 52 | "eslint-plugin-vue": "^7.3.0", 53 | "json5": "^2.2.0", 54 | "require-all": "^3.0.0", 55 | "stylelint-config-ali": "^0.3.4", 56 | "stylelint-scss": "^3.18.0", 57 | "vue-eslint-parser": "^7.2.0" 58 | }, 59 | "devDependencies": { 60 | "eslint": "^7.22.0", 61 | "jest": "^24.9.0", 62 | "prettier": "^2.1.0", 63 | "rax": "^1.1.0", 64 | "react": "^16.12.0", 65 | "react-dom": "^16.12.0", 66 | "stylelint": "^13.2.0", 67 | "typescript": "^3.5.3" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/README.md: -------------------------------------------------------------------------------- 1 | # @iceworks/eslint-plugin-best-practices 2 | 3 | Iceworks best practices eslint plugin. 4 | 5 | ## Installation 6 | 7 | Install [esLint](http://eslint.org), `@iceworks/eslint-plugin-best-practices`: 8 | 9 | ```shell 10 | $ npm install --save-dev eslint @iceworks/eslint-plugin-best-practices 11 | ``` 12 | 13 | ## Usage 14 | 15 | Recommend use [@iceworks/spec](https://www.npmjs.com/package/@iceworks/spec) 16 | 17 | Then configure the rules you want to use under the rules section. 18 | 19 | ```js 20 | // .eslintrc.js 21 | const { getESLintConfig } = require('@iceworks/spec'); 22 | 23 | // getESLintConfig(rule: 'rax'|'rax-ts'|'react'|'react-ts', customConfig?); 24 | module.exports = getESLintConfig('rax', { 25 | // custom config it will merge into main config 26 | rules: { 27 | '@iceworks/best-practices/rule-name': 'off', 28 | }, 29 | }); 30 | ``` 31 | 32 | ## Supported Rules 33 | 34 | - [`deps-no-ice-scripts`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/deps-no-ice-scripts.md) It is not recommended to use ice-scripts, the new version is ice.js. 35 | - [`deps-no-resolutions`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/deps-no-resolutions.md) It is not recommended to use resolutions to lock the version. 36 | - [`deps-no-router-library`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/deps-no-router-library.md) It is not recommended to directly rely on routing libraries, such as react-router-dom, react-router. 37 | - [`no-broad-semantic-versioning`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/no-broad-semantic-versioning.md) Recommended the semantic versioning include everything greater than a particular version in the same major range. 38 | - [`no-http-url`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/no-http-url.md) Recommended the http url switch to HTTPS. 39 | - [`no-js-in-ts-project`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/no-js-in-ts-project.md) It is not recommended to use js and ts files at the same time. 40 | - [`no-lowercase-component-name`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/no-lowercase-component-name.md) It is not recommended to name components in lower case. 41 | - [`recommend-add-line-height-unit`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/recommend-add-line-height-unit.md) Recommended to add unit for line-height which is more than 5. 42 | - [`recommend-functional-component`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/recommend-functional-component.md) It is not recommended to use class component. 43 | - [`recommend-polyfill`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/recommend-polyfill.md) Recommend API which not supported in iOS 9 to add polyfill file. 44 | - [`recommend-update-rax`](https://github.com/ice-lab/spec/tree/master/packages/eslint-plugin-best-practices/docs/rules/recommend-update-rax.md) Rax version < 1.0 , recommend to update Rax. 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @iceworks/spec 2 | 3 | Easy to use **eslint**(support TypeScript) / **stylelint** / **prettier** / **commitlint** in [rax](https://rax.js.org/), [ice](https://ice.work/) and react project. And spec means specification. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm i --save-dev @iceworks/spec eslint stylelint prettier @commitlint/cli 9 | ``` 10 | 11 | PS: You don't need to install other eslint plugins and parsers. 12 | 13 | ## Usage 14 | 15 | ### ESLint 16 | 17 | #### 1. Create configuration file 18 | 19 | First create a `.eslintrc.js` file. Then edit your config. 20 | 21 | #### 2. Update config 22 | 23 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/eslint/react.js) base on [eslint-config-ali](https://www.npmjs.com/package/eslint-config-ali) and [@iceworks/eslint-plugin-best-practices](https://www.npmjs.com/package/@iceworks/eslint-plugin-best-practices). 24 | 25 | ```js 26 | // .eslintrc.js 27 | const { getESLintConfig } = require('@iceworks/spec'); 28 | 29 | // getESLintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 30 | module.exports = getESLintConfig('react'); 31 | ``` 32 | 33 | ### stylelint 34 | 35 | #### 1. Create configuration file 36 | 37 | First create a `.stylelintrc.js` file. Then edit your config. 38 | 39 | #### 2. Update config 40 | 41 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/stylelint/react.js) base on [stylelint-config-ali](https://www.npmjs.com/package/stylelint-config-ali) 42 | 43 | ```js 44 | // .stylelintrc.js 45 | const { getStylelintConfig } = require('@iceworks/spec'); 46 | 47 | // getStylelintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 48 | module.exports = getStylelintConfig('react'); 49 | ``` 50 | 51 | ### prettier [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/prettier/react.js) 52 | 53 | #### 1. Create configuration file 54 | 55 | First create a `.prettierrc.js` file. Then edit your config. 56 | 57 | #### 2. Update config 58 | 59 | ```js 60 | // .prettierrc.js 61 | const { getPrettierConfig } = require('@iceworks/spec'); 62 | 63 | // getPrettierConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 64 | module.exports = getPrettierConfig('react'); 65 | ``` 66 | 67 | ### commitlint 68 | 69 | #### 1. Create configuration file 70 | 71 | First create a `.commitlintrc.js` file. Then edit your config. 72 | 73 | #### 2. Update config 74 | 75 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/commitlint/react.js) base on [commitlint-config-ali](https://www.npmjs.com/package/commitlint-config-ali) 76 | 77 | ```js 78 | // .commitlintrc.js 79 | const { getCommitlintConfig } = require('@iceworks/spec'); 80 | 81 | // getCommitlintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 82 | module.exports = getCommitlintConfig('react'); 83 | ``` 84 | 85 | ## FAQ 86 | 87 | ### Custom config 88 | 89 | ```js 90 | // .eslintrc.js 91 | const { getESLintConfig } = require('@iceworks/spec'); 92 | 93 | // getESLintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 94 | module.exports = getESLintConfig('rax', { 95 | // custom config it will merge into main config 96 | rules: { 97 | // ... 98 | }, 99 | }); 100 | ``` 101 | 102 | ### package.json scripts 103 | 104 | Add `scripts` in your `package.json`, example: 105 | 106 | ```json 107 | "scripts": { 108 | "lint": "npm run eslint && npm run stylelint", 109 | "eslint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./", 110 | "stylelint": "stylelint ./**/*.scss", 111 | "prettier": "prettier **/* --write" 112 | } 113 | ``` 114 | 115 | Then use `npm run lint` check your project, ues `npm run prettier` update your code. 116 | 117 | ### Git hooks 118 | 119 | To lint commits before they are created you can use Husky's Git hook. 120 | 121 | Install in your project `npm install husky --save-dev` or `yarn add -D husky`. 122 | 123 | After that, we recommend you to see [husky docs](https://www.npmjs.com/package/husky), then create "`commit-msg`" and "`pre-commit`" config. 124 | 125 | ### Update from @ice/spec 126 | 127 | If you are using [@ice/spec](https://www.npmjs.com/package/@ice/spec) in your project, we recommend use `@iceworks/spec` to get better maintainability and faster response to lint rules support. 128 | 129 | Based on `@iceworks/spec`'s simple API you can quickly migrate your project, install and update your lint config file, the mission is completed 😁. 130 | 131 | ## Develop 132 | 133 | ### Run Test 134 | 135 | ``` 136 | npm run test 137 | ``` 138 | 139 | run test for specific component 140 | 141 | ``` 142 | npm run test -- packages/spec 143 | ``` 144 | 145 | ### Run Prettier 146 | 147 | ``` 148 | npm run prettier 149 | ``` 150 | 151 | ### Run Lint 152 | 153 | ``` 154 | npm run lint 155 | ``` 156 | -------------------------------------------------------------------------------- /packages/spec/examples/react-ts/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Card, Table, Button, Icon, Pagination, Message } from '@alifd/next'; 3 | import { TableProps } from '@alifd/next/lib/table'; 4 | import { useFusionTable, useFullscreen } from 'ahooks'; 5 | 6 | import CustomList, { Column } from './CustomList'; 7 | import { getColumnKey } from './util'; 8 | 9 | import styles from './index.module.scss'; 10 | 11 | const unusedVar = 1; 12 | 13 | const TableActionIcon = Icon.createFromIconfontCN({ 14 | scriptUrl: '//at.alicdn.com/t/font_1899388_oxn3zhg34oj.js', 15 | }); 16 | 17 | const getTableData = ({ 18 | current, 19 | pageSize, 20 | }: { 21 | current: number; 22 | pageSize: number; 23 | }): Promise => { 24 | const query = `page=${current}&size=${pageSize}`; 25 | return fetch(`https://randomuser.me/api?results=${pageSize}&${query}`) 26 | .then((res) => res.json()) 27 | .then((res) => ({ 28 | total: 55, 29 | list: res.results.slice(0, 10), 30 | })); 31 | }; 32 | 33 | // 根据 hidden 切换当前 column 是否显示 34 | const filterColumns = (columnList: Column[]) => { 35 | const newColumnList = [...columnList]; 36 | return newColumnList 37 | .filter((columnItem) => { 38 | if (columnItem.hidden) { 39 | return false; 40 | } 41 | return true; 42 | }) 43 | .map((columnItem) => { 44 | if (columnItem.children) { 45 | const groupProps = { ...columnItem }; 46 | delete groupProps.children; 47 | 48 | return ( 49 | 50 | {filterColumns(columnItem.children)} 51 | 52 | ); 53 | } 54 | 55 | return ; 56 | }); 57 | }; 58 | 59 | const defaultColumns: Column[] = [ 60 | { 61 | id: '1', 62 | title: '名称', 63 | children: [ 64 | { 65 | id: '1-1', 66 | title: '前缀', 67 | dataIndex: 'name.title', 68 | }, 69 | { 70 | id: '1-2', 71 | title: '名', 72 | dataIndex: 'name.first', 73 | }, 74 | { 75 | id: '1-3', 76 | title: '姓', 77 | dataIndex: 'name.last', 78 | }, 79 | ], 80 | }, 81 | { 82 | id: '2', 83 | title: 'Email', 84 | dataIndex: 'email', 85 | }, 86 | { 87 | id: '3', 88 | title: '电话号码', 89 | dataIndex: 'phone', 90 | }, 91 | { 92 | id: '4', 93 | title: '性别', 94 | dataIndex: 'gender', 95 | }, 96 | ]; 97 | 98 | const AppList = () => { 99 | // 切换紧凑模式 100 | const [sizeStatus, changeSize] = useState('medium'); 101 | const autoChangeSize = () => { 102 | if (sizeStatus === 'medium') { 103 | changeSize('small'); 104 | } else { 105 | changeSize('medium'); 106 | } 107 | }; 108 | 109 | // 切换 zebra 110 | const [zebraStatus, changeZebra] = useState(false); 111 | 112 | // 切换全屏 113 | const [, { toggleFull }] = useFullscreen(document.getElementById('table-container'), { 114 | onFull: () => { 115 | const ele = document.getElementById('table-container'); 116 | ele.setAttribute('style', 'padding: 20px;background: #ffffff'); 117 | }, 118 | }); 119 | 120 | // 获取表格数据 121 | const { paginationProps, tableProps } = useFusionTable(getTableData, {}); 122 | 123 | // 切换当前 columns 124 | const [columns, onColumnChange] = useState(defaultColumns); 125 | 126 | return ( 127 | 128 | 129 |
130 |
131 | 134 | 135 | 136 |
137 |
138 | 141 | 144 | 147 | 148 |
149 |
150 | 151 | {filterColumns(columns)} 152 |
153 | ( 156 | <> 157 | 共{' '} 158 | {' '} 161 | 个记录 162 | 163 | )} 164 | {...paginationProps} 165 | /> 166 |
167 |
168 | ); 169 | }; 170 | 171 | export default AppList; 172 | -------------------------------------------------------------------------------- /packages/eslint-plugin-best-practices/src/rules/recommend-polyfill.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver'); 2 | const bcd = require('@mdn/browser-compat-data'); 3 | const docsUrl = require('../docsUrl'); 4 | 5 | const targetBrowsers = { 6 | safari: 9, 7 | safari_ios: 9, 8 | }; 9 | 10 | const APIsToLowercase = { 11 | Console: true, 12 | Window: true, 13 | Document: true, 14 | External: true, 15 | History: true, 16 | Location: true, 17 | Navigator: true, 18 | Performance: true, 19 | Screen: true, 20 | Controllers: true, 21 | }; 22 | // https://www.npmjs.com/package/@mdn/browser-compat-data 23 | const data = Object.assign({}, bcd.javascript.builtins, bcd.api); 24 | 25 | Object.keys(data).forEach((key) => { 26 | if (APIsToLowercase[key]) { 27 | data[key.toLowerCase()] = data[key]; 28 | delete data[key]; 29 | } 30 | }); 31 | 32 | // For extra property check. Like a.flat no matter what is a. 33 | const extraTargetProperties = { 34 | flat: { parent: 'Array' }, 35 | flatMap: { parent: 'Array' }, 36 | codePointAt: { parent: 'String' }, 37 | matchAll: { parent: 'String' }, 38 | normalize: { parent: 'String' }, 39 | padEnd: { parent: 'String' }, 40 | padStart: { parent: 'String' }, 41 | replaceAll: { parent: 'String' }, 42 | trimEnd: { parent: 'String' }, 43 | trimStart: { parent: 'String' }, 44 | finally: { parent: 'Promise' }, 45 | }; 46 | 47 | const RULE_NAME = 'recommend-polyfill'; 48 | 49 | module.exports = { 50 | meta: { 51 | type: 'problem', 52 | docs: { 53 | url: docsUrl(RULE_NAME), 54 | }, 55 | fixable: null, 56 | messages: { 57 | recommendPolyfill: 58 | '[Critical] It is recommended to add polyfill for "{{API}}", This might be caused by a compatibility problem in "{{browser}}"', 59 | }, 60 | }, 61 | 62 | create(context) { 63 | const handleReport = function (node, object, property) { 64 | let target = data[object]; 65 | 66 | if (data[object] && data[object][property]) { 67 | target = data[object][property]; 68 | } 69 | 70 | if (target) { 71 | try { 72 | const supports = target.__compat.support; 73 | for (let i = 0, l = Object.keys(targetBrowsers).length; i < l; i++) { 74 | const browser = Object.keys(targetBrowsers)[i]; 75 | if ( 76 | semver.satisfies( 77 | `${targetBrowsers[browser]}.0.0`, 78 | `<${supports[browser].version_added}`, 79 | ) 80 | ) { 81 | context.report({ 82 | node, 83 | messageId: 'recommendPolyfill', 84 | data: { 85 | API: `${object}${property ? '.' : ''}${property}`, 86 | browser: `${browser}@${targetBrowsers[browser]}`, 87 | }, 88 | }); 89 | break; 90 | } 91 | } 92 | } catch (e) { 93 | // ignore 94 | } 95 | } else if (extraTargetProperties[property]) { 96 | context.report({ 97 | node, 98 | messageId: 'recommendPolyfill', 99 | data: { 100 | API: `${extraTargetProperties[property].parent}.${property}`, 101 | browser: 'iOS9', 102 | }, 103 | }); 104 | } 105 | }; 106 | 107 | const handleRequires = function (node) { 108 | try { 109 | let type; 110 | let object; 111 | let property; 112 | if (node.callee.type === 'Identifier') { 113 | // new xxx(); 114 | // xxx.xxx(); 115 | object = node.callee.name; 116 | if (object) { 117 | handleReport(node, object, ''); 118 | } 119 | } else if (node.callee.type === 'MemberExpression') { 120 | type = node.callee.object.type; 121 | object = node.callee.object.name; 122 | property = node.callee.property.name || node.callee.property.value; // object.value or object[value] 123 | 124 | if (type === 'CallExpression') { 125 | object = node.callee.object.callee && node.callee.object.callee.name; 126 | } 127 | 128 | // xxx.prototype.xxx.call(); 129 | if (type === 'MemberExpression') { 130 | const obj = node.callee.object; 131 | if (obj.object && obj.object.property && obj.object.property.name === 'prototype') { 132 | object = obj.object.object.name; 133 | property = obj.property.name; 134 | } 135 | } 136 | 137 | // [].xxx() 138 | if (type === 'ArrayExpression') { 139 | object = 'Array'; 140 | } 141 | 142 | // {}.xxx() 143 | if (type === 'ObjectExpression') { 144 | object = 'Object'; 145 | } 146 | 147 | // ''.xxx() 148 | if (type === 'Literal' && typeof node.callee.object.value === 'string') { 149 | object = 'String'; 150 | } 151 | 152 | if (object && property) { 153 | handleReport(node, object, property); 154 | } 155 | } 156 | } catch (e) { 157 | // ignore 158 | } 159 | }; 160 | 161 | return { 162 | CallExpression: handleRequires, 163 | NewExpression: handleRequires, 164 | }; 165 | }, 166 | }; 167 | -------------------------------------------------------------------------------- /packages/spec/README.md: -------------------------------------------------------------------------------- 1 | # @iceworks/spec 2 | 3 | Easy to use **eslint**(support TypeScript) / **stylelint** / **prettier** / **commitlint** in [rax](https://rax.js.org/), [ice](https://ice.work/) and react project. And spec means specification. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm i --save-dev @iceworks/spec eslint stylelint prettier @commitlint/cli 9 | ``` 10 | 11 | PS: You don't need to install other eslint plugins and parsers. 12 | 13 | ## Usage 14 | 15 | ### ESLint 16 | 17 | #### 1. Create configuration file 18 | 19 | First create a `.eslintrc.js` file. Then edit your config. 20 | 21 | #### 2. Update config 22 | 23 | ##### JavaScript + [rax](https://rax.js.org/), [ice](https://ice.work/) and react 24 | 25 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/eslint/react.js) base on [eslint-config-ali](https://www.npmjs.com/package/eslint-config-ali) and [@iceworks/eslint-plugin-best-practices](https://www.npmjs.com/package/@iceworks/eslint-plugin-best-practices). 26 | 27 | ```js 28 | // .eslintrc.js 29 | const { getESLintConfig } = require('@iceworks/spec'); 30 | 31 | // getESLintConfig(rule: 'common'|'rax'|'react'|'vue' , customConfig?); 32 | module.exports = getESLintConfig('common'); 33 | ``` 34 | 35 | ##### TypeScript + [rax](https://rax.js.org/), [ice](https://ice.work/) and react 36 | 37 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/eslint/react-ts.js) base on [eslint-config-ali](https://www.npmjs.com/package/eslint-config-ali) and [@iceworks/eslint-plugin-best-practices](https://www.npmjs.com/package/@iceworks/eslint-plugin-best-practices). 38 | 39 | ```js 40 | // .eslintrc.js 41 | const { getESLintConfig } = require('@iceworks/spec'); 42 | 43 | // getESLintConfig(rule: 'common-ts'|'rax-ts'|'react-ts'|'vue-ts', customConfig?); 44 | module.exports = getESLintConfig('common-ts'); 45 | ``` 46 | 47 | ### stylelint 48 | 49 | #### 1. Create configuration file 50 | 51 | First create a `.stylelintrc.js` file. Then edit your config. 52 | 53 | #### 2. Update config 54 | 55 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/stylelint/react.js) base on [stylelint-config-ali](https://www.npmjs.com/package/stylelint-config-ali) 56 | 57 | ```js 58 | // .stylelintrc.js 59 | const { getStylelintConfig } = require('@iceworks/spec'); 60 | 61 | // getStylelintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 62 | module.exports = getStylelintConfig('react'); 63 | ``` 64 | 65 | ### prettier [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/prettier/react.js) 66 | 67 | #### 1. Create configuration file 68 | 69 | First create a `.prettierrc.js` file. Then edit your config. 70 | 71 | #### 2. Update config 72 | 73 | ```js 74 | // .prettierrc.js 75 | const { getPrettierConfig } = require('@iceworks/spec'); 76 | 77 | // getPrettierConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 78 | module.exports = getPrettierConfig('react'); 79 | ``` 80 | 81 | ### commitlint 82 | 83 | #### 1. Create configuration file 84 | 85 | First create a `.commitlintrc.js` file. Then edit your config. 86 | 87 | #### 2. Update config 88 | 89 | [rules](https://github.com/ice-lab/spec/tree/master/packages/spec/src/commitlint/react.js) base on [commitlint-config-ali](https://www.npmjs.com/package/commitlint-config-ali) 90 | 91 | ```js 92 | // .commitlintrc.js 93 | const { getCommitlintConfig } = require('@iceworks/spec'); 94 | 95 | // getCommitlintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 96 | module.exports = getCommitlintConfig('react'); 97 | ``` 98 | 99 | ## FAQ 100 | 101 | ### Custom config 102 | 103 | ```js 104 | // .eslintrc.js 105 | const { getESLintConfig } = require('@iceworks/spec'); 106 | 107 | // getESLintConfig(rule: 'common'|'rax'|'react'|'vue', customConfig?); 108 | module.exports = getESLintConfig('rax', { 109 | // custom config it will merge into main config 110 | rules: { 111 | // ... 112 | }, 113 | }); 114 | ``` 115 | 116 | ### package.json scripts 117 | 118 | Add `scripts` in your `package.json`, example: 119 | 120 | ```json 121 | "scripts": { 122 | "lint": "npm run eslint && npm run stylelint", 123 | "eslint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./", 124 | "stylelint": "stylelint ./**/*.scss", 125 | "prettier": "prettier **/* --write" 126 | } 127 | ``` 128 | 129 | Then use `npm run lint` check your project, ues `npm run prettier` update your code. 130 | 131 | ### Git hooks 132 | 133 | To lint commits before they are created you can use Husky's Git hook. 134 | 135 | Install in your project `npm install husky --save-dev` or `yarn add -D husky`. 136 | 137 | After that, we recommend you to see [husky docs](https://www.npmjs.com/package/husky), then create "`commit-msg`" and "`pre-commit`" config. 138 | 139 | ### Update from @ice/spec 140 | 141 | If you are using [@ice/spec](https://www.npmjs.com/package/@ice/spec) in your project, we recommend use `@iceworks/spec` to get better maintainability and faster response to lint rules support. 142 | 143 | Based on `@iceworks/spec`'s simple API you can quickly migrate your project, install and update your lint config file, the mission is completed 😁. 144 | 145 | ### Error: Cannot find module 'eslint-plugin-foo' 146 | 147 | Eslint is not yet supported having plugins as dependencies in shareable config. [issue](https://github.com/eslint/eslint/issues/3458). As a temporary solution, you need add the plugin to devDependencies in your project, like `npm i --save-dev eslint-plugin-jsx-a11y`. 148 | 149 | ### Error: The file does not match your project config 150 | 151 | TypeScript project run lint file when see this error, you can update your `tsconfig.json`. 152 | 153 | update `src/*` to `src/**/*`: 154 | 155 | ```json 156 | { 157 | "include": ["src/**/*"] 158 | } 159 | ``` 160 | 161 | [CHANGELOG](https://github.com/ice-lab/spec/blob/master/CHANGELOG.md) 162 | 163 | Enjoy! 164 | --------------------------------------------------------------------------------