diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fd9c2f2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +# indent_size = 8 # should be set in the local editor +# `max_line_length` will overwrite prettier's default print_width +max_line_length = 120 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false +indent_style = space +indent_size = 2 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..243504d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +--- +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + open-pull-requests-limit: 10 + schedule: + interval: 'weekly' + day: 'sunday' + - package-ecosystem: npm + directory: '/' + open-pull-requests-limit: 10 + schedule: + interval: 'weekly' + day: 'sunday' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0585cbc --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Dependency directories +node_modules/ +.yarn/ +package-lock.json +**/*.bun + +# Editor folders +.idea/ +.vscode/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Operating system files +.DS_Store + +# Keys +.env diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3a2f87e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2024 SWR Audio lab + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2eafb15 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +

@swrlab/style-guide

+
+> SWR Audio Lab Style Guide +
+ +
+ +[![license](https://img.shields.io/github/license/swrlab/style-guide?label=license)](https://github.com/swrlab/style-guide/blob/main/LICENSE) +[![version](https://img.shields.io/npm/v/@swrlab/style-guide)](https://www.npmjs.com/package/swrlab/style-guide) + +
+ +
+SWR Audio Lab's Style Guide 💅 +
+ + +## Introduction + +This repository is the home of an based on Vercel's style guide, which includes configs for +popular linting and styling tools. + +The following configs are available, and are designed to be used together. + +- [Prettier](#prettier) +- [ESLint](#eslint) +- [Biome](#biome) + +## Installation + +All of our configs are contained in one package, `@swrlab/style-guide`. To install: + +```sh +# If you use Bun +bun add --dev @swrlab/style-guide + +# If you use Yarn +yarn add --dev @swrlab/style-guide + +# If you use npm +npm i --save-dev @swrlab/style-guide + +# If you use pmpm +pnpm i --save-dev @swrlab/style-guide + +``` + +Some of our ESLint configs require peer dependencies. We'll note those +alongside the available configs in the [ESLint](#eslint) section. + +## Prettier + +> Note: Prettier is a peer-dependency of this package, and should be installed +> at the root of your project. +> +> See: https://prettier.io/docs/en/install.html + +To use the shared Prettier config, set the following in `package.json`. + +```json +{ + "prettier": "@swrlab/style-guide/prettier" +} +``` + +## ESLint + +> Note: ESLint is a peer-dependency of this package, and should be installed +> at the root of your project. +> +> See: https://eslint.org/docs/user-guide/getting-started#installation-and-usage + +Usage: + +```js +// eslint.config.mjs +import { audiolab } from '@swrlab/style-guide/eslint/presets.js' + +export default audiolab( + [ + /* your custom ESLint config */ + ], + { + prettier: true, + vue: true + }, +) +``` + +### Presets + +You can also import and compose individual presets. However, it is recommended that you use the factory function above. + +```js +// eslint.config.js +import { presetAll, presetBasic } from '@swrlab/style-guide/eslint/presets.js' + +export default presetBasic; +``` + + +## Biome + +To use the shared Biome config, set the following in `biome.json`: + +```json +{ + "extends": ["@swrlab/style-guide/biome"] +} +``` + +## Credits + +This config is inspired by the work of [The Vercel Style Guide](https://github.com/vercel/style-guide) and is further +based on + +* https://github.com/antfu/eslint-config +* https://github.com/sxzz/eslint-config + +## Contributing + +After cloning, you can run `bun install` (or `npm install`) to install the npm dependencies. + +## License + +ISC diff --git a/biome/biome.json b/biome/biome.json new file mode 100644 index 0000000..814fea5 --- /dev/null +++ b/biome/biome.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "indentStyle": "tab", + "formatWithErrors": true + }, + "javascript": { + "formatter": { + "semicolons": "asNeeded", + "trailingComma": "es5", + "quoteStyle": "single" + } + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..d0140b6 Binary files /dev/null and b/bun.lockb differ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b9abcf4 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,19 @@ +import { audiolab } from './eslint/presets.js' + +export default audiolab( + [ + { + ignores: [], + }, + { + rules: { + 'import/extensions': ['error', 'ignorePackages'], + }, + }, + ], + { + prettier: true, + comments: true, + vue: true, + } +) diff --git a/eslint/configs/comments.js b/eslint/configs/comments.js new file mode 100644 index 0000000..6d6c1a4 --- /dev/null +++ b/eslint/configs/comments.js @@ -0,0 +1,31 @@ +import { pluginComments } from '../plugins.js' + +/** + * @typedef {import("eslint-define-config").FlatESLintConfigItem} FlatESLintConfigItem + */ + +/** + * @returns {FlatESLintConfigItem[]} + */ +export const comments = [ + { + plugins: { + 'eslint-comments': pluginComments, + }, + rules: { + ...pluginComments.configs.recommended.rules, + /** + * Warn if ESlint enable directives are missing after disable directives. + * + * 🚫 Not fixable - https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html + */ + 'eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }], + /** + * Require comments on ESlint disable directives. + * + * 🚫 Not fixable - https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/require-description.html + */ + 'eslint-comments/require-description': 'error', + }, + }, +] diff --git a/eslint/configs/errors.js b/eslint/configs/errors.js new file mode 100644 index 0000000..c96fd25 --- /dev/null +++ b/eslint/configs/errors.js @@ -0,0 +1,44 @@ +/** @type {import("eslint-define-config").FlatESLintConfigItem[]} */ +export const errors = [ + { + rules: { + /** + * Disallow await inside of loops. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-await-in-loop + */ + 'no-await-in-loop': 'error', + /** + * Allow the use of console. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-console + */ + 'no-console': 'error', + /** + * Disallow expressions where the operation doesn't affect the value. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-constant-binary-expression + */ + 'no-constant-binary-expression': 'error', + /** + * Disallow returning values from Promise executor functions. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-promise-executor-return + */ + 'no-promise-executor-return': 'error', + /** + * Disallow template literal placeholder syntax in regular strings, as + * these are likely errors. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-template-curly-in-string + */ + 'no-template-curly-in-string': 'error', + /** + * Disallow loops with a body that allows only one iteration. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-unreachable-loop + */ + 'no-unreachable-loop': 'error', + }, + }, +] diff --git a/eslint/configs/ignores.js b/eslint/configs/ignores.js new file mode 100644 index 0000000..9857568 --- /dev/null +++ b/eslint/configs/ignores.js @@ -0,0 +1,6 @@ +import { GLOB_EXCLUDE } from '../constants.js' + +/** + * @type {import("eslint-define-config").FlatESLintConfigItem[]} + */ +export const ignores = [{ ignores: GLOB_EXCLUDE }] diff --git a/eslint/configs/imports.js b/eslint/configs/imports.js new file mode 100644 index 0000000..9aca4ad --- /dev/null +++ b/eslint/configs/imports.js @@ -0,0 +1,102 @@ +import { GLOB_SRC_EXT } from '../constants.js' +import { pluginImport } from '../plugins.js' + +export const imports = [ + { + plugins: { + import: pluginImport, + }, + rules: { + // TODO: add recommended rules (if ready for Flat Config) + + /** + * Disallow non-import statements appearing before import statements. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/first.md + */ + 'import/first': 'error', + /** + * Require a newline after the last import/require. + * + * 🔧 Fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/newline-after-import.md + */ + 'import/newline-after-import': 'warn', + /** + * Disallow import of modules using absolute paths. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-absolute-path.md + */ + 'import/no-absolute-path': 'error', + /** + * Disallow cyclical dependencies between modules. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-cycle.md + */ + 'import/no-cycle': 'error', + /** + * Disallow default exports. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-default-export.md + */ + 'import/no-default-export': 'error', + /** + * Disallow the use of extraneous packages. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md + */ + 'import/no-extraneous-dependencies': ['error', { includeTypes: true }], + /** + * Disallow mutable exports. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-mutable-exports.md + */ + 'import/no-mutable-exports': 'error', + /** + * Disallow importing packages through relative paths. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-relative-packages.md + */ + 'import/no-relative-packages': 'warn', + /** + * Disallow a module from importing itself. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-self-import.md + */ + 'import/no-self-import': 'error', + /** + * Ensures that there are no useless path segments. + * + * 🚫 Not fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-useless-path-segments.md + */ + 'import/no-useless-path-segments': ['error'], + /** + * Enforce a module import order convention. + * + * 🔧 Fixable - https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md + */ + 'import/order': [ + 'warn', + { + groups: [ + 'builtin', // Node.js built-in modules + 'external', // Packages + 'internal', // Aliased modules + 'parent', // Relative parent + 'sibling', // Relative sibling + 'index', // Relative index + ], + 'newlines-between': 'never', + }, + ], + }, + }, + { + files: [`**/*config*.${GLOB_SRC_EXT}`, '**/index.js', '**/*.d.ts', '**/.prettierrc*'], + plugins: { + import: pluginImport, + }, + rules: { + 'import/no-default-export': 'off', + }, + }, +] diff --git a/eslint/configs/index.js b/eslint/configs/index.js new file mode 100644 index 0000000..76640bd --- /dev/null +++ b/eslint/configs/index.js @@ -0,0 +1,16 @@ +export * from './comments.js' +export * from './ignores.js' +export * from './imports.js' +export * from './errors.js' +export * from './javascript.js' +export * from './node.js' +export * from './prettier.js' +export * from './json.js' +export * from './perfectionist.js' +export * from './sort.js' +export * from './unicorn.js' +export * from './vue.js' +export * from './security.js' +export * from './sonarjs.js' +// TODO: add yaml config +// export * from './yml.js' diff --git a/eslint/configs/javascript.js b/eslint/configs/javascript.js new file mode 100644 index 0000000..90d9dc2 --- /dev/null +++ b/eslint/configs/javascript.js @@ -0,0 +1,444 @@ +import globals from 'globals' +import { pluginJs } from '../plugins.js' +import { ECMA_VERSION } from '../constants.js' +/** + * @typedef {import('eslint-define-config').FlatESLintConfigItem} FlatESLintConfigItem + */ + +const USE_JSX = true + +/** + * @returns {FlatESLintConfigItem[]} + */ +export const javascript = [ + pluginJs.configs.recommended, + { + languageOptions: { + ecmaVersion: ECMA_VERSION, + globals: { + ...globals.browser, + ...globals.es2021, + ...globals.node, + document: 'readonly', + navigator: 'readonly', + window: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: USE_JSX, + }, + ecmaVersion: ECMA_VERSION, + sourceType: 'module', + }, + sourceType: 'module', + }, + linterOptions: { + // noInlineConfig: true, // commented out, since we allow inline configs for now + reportUnusedDisableDirectives: true, + }, + rules: { + // Best practises + /** + * Require return statements in array methods callbacks. + * + * 🚫 Not fixable -https://eslint.org/docs/rules/array-callback-return + */ + 'array-callback-return': ['error', { allowImplicit: true }], + /** + * Treat `var` statements as if they were block scoped. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/block-scoped-var + */ + 'block-scoped-var': 'error', + /** + * Require curly braces for multiline blocks or nested and be consistent. + * Disabled for now + * + * 🔧 Fixable - https://eslint.org/docs/rules/curly + */ + // curly: ['warn', 'multi-or-nest', 'consistent'], + /** + * Require default clauses in switch statements to be last (if used). + * + * 🚫 Not fixable - https://eslint.org/docs/rules/default-case-last + */ + 'default-case-last': 'error', + /** + * Require triple equals (`===` and `!==`). + * + * 🔧 Fixable - https://eslint.org/docs/rules/eqeqeq + */ + eqeqeq: 'error', + /** + * Require grouped accessor pairs in object literals and classes. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/grouped-accessor-pairs + */ + 'grouped-accessor-pairs': 'error', + /** + * Disallow use of `alert()`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-alert + */ + 'no-alert': 'error', + /** + * Disallow use of `caller`/`callee`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-caller + */ + 'no-caller': 'error', + /** + * Disallow returning value in constructor. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-constructor-return + */ + 'no-constructor-return': 'error', + /** + * Disallow using an `else` if the `if` block contains a return. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-else-return + */ + 'no-else-return': 'warn', + /** + * Disallow `eval()`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-eval + */ + 'no-eval': 'error', + /** + * Disallow extending native objects. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-extend-native + */ + 'no-extend-native': 'error', + /** + * Disallow unnecessary function binding. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-extra-bind + */ + 'no-extra-bind': 'error', + /** + * Disallow unnecessary labels. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-extra-label + */ + 'no-extra-label': 'error', + /** + * Disallow floating decimals. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-floating-decimal + */ + 'no-floating-decimal': 'error', + /** + * Make people convert types explicitly e.g. `Boolean(foo)` instead of `!!foo`. + * For now not enabled. + * + * 🔧 Partially Fixable - https://eslint.org/docs/rules/no-implicit-coercion + */ + 'no-implicit-coercion': 'off', // { disallowTemplateShorthand: true, allow: ['!!'] } + /** + * Disallow use of `eval()`-like methods. + * + * https://eslint.org/docs/rules/no-implied-eval + */ + 'no-implied-eval': 'error', + /** + * Disallow usage of `__iterator__` property. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-iterator + */ + 'no-iterator': 'error', + /** + * Disallow use of labels for anything other than loops and switches. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-labels + */ + 'no-labels': ['error'], + /** + * Disallow unnecessary nested blocks. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-lone-blocks + */ + 'no-lone-blocks': 'error', + /** + * Disallow `new` for side effects. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-new + */ + 'no-new': 'error', + /** + * Disallow function constructors. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-new-func + */ + 'no-new-func': 'error', + /** + * Disallow primitive wrapper instances, such as `new String('foo')`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-new-wrappers + */ + 'no-new-wrappers': 'error', + /** + * Disallow use of octal escape sequences in string literals. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-octal-escape + */ + 'no-octal-escape': 'error', + /** + * Disallow reassignment of function parameters. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-param-reassign + */ + 'no-param-reassign': 'error', + /** + * Disallow usage of the deprecated `__proto__` property. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-proto + */ + 'no-proto': 'error', + /** + * Disallow assignment in `return` statement. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-return-assign + */ + 'no-return-assign': 'error', + /** + * Disallows unnecessary `return await`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-return-await + */ + 'no-return-await': 'error', + /** + * Disallow use of `javascript:` urls. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-script-url + */ + 'no-script-url': 'error', + /** + * Disallow comparisons where both sides are exactly the same. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-self-compare + */ + 'no-self-compare': 'error', + /** + * Disallow use of comma operator. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-sequences + */ + 'no-sequences': 'error', + /** + * Disallow unnecessary `.call()` and `.apply()`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-useless-call + */ + 'no-useless-call': 'error', + /** + * Disallow unnecessary concatenation of strings. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-useless-concat + */ + 'no-useless-concat': 'error', + /** + * Disallow redundant return statements. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-useless-return + */ + 'no-useless-return': 'warn', + /** + * Require using named capture groups in regular expressions. + * For now off. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/prefer-named-capture-group + */ + 'prefer-named-capture-group': 'off', + /** + * Require using Error objects as Promise rejection reasons. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/prefer-promise-reject-errors + */ + 'prefer-promise-reject-errors': ['error', { allowEmptyReject: true }], + /** + * Disallow use of the RegExp constructor in favor of regular expression + * literals. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/prefer-regex-literals + */ + 'prefer-regex-literals': 'error', + /** + * Disallow "Yoda conditions", ensuring the comparison. + * + * 🔧 Fixable - https://eslint.org/docs/rules/yoda + */ + yoda: 'warn', + + // ES6 + /** + * Disallow useless computed property keys. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-useless-computed-key + */ + 'no-useless-computed-key': 'warn', + /** + * Disallow renaming import, export, and destructured assignments to the + * same name. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-useless-rename + */ + 'no-useless-rename': 'warn', + /** + * Require `let` or `const` instead of `var`. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-var + */ + 'no-var': 'error', + /** + * Require object literal shorthand syntax. + * + * 🔧 Fixable - https://eslint.org/docs/rules/object-shorthand + */ + 'object-shorthand': 'warn', + /** + * Require default to `const` instead of `let`. + * + * 🔧 Fixable - https://eslint.org/docs/rules/prefer-const + */ + 'prefer-const': 'warn', + /** + * Disallow parseInt() in favor of binary, octal, and hexadecimal literals. + * + * 🔧 Fixable - https://eslint.org/docs/rules/prefer-numeric-literals + */ + 'prefer-numeric-literals': 'error', + /** + * Require using rest parameters instead of `arguments`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/prefer-rest-params + */ + 'prefer-rest-params': 'error', + /** + * Require using spread syntax instead of `.apply()`. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/prefer-spread + */ + 'prefer-spread': 'error', + /** + * Require using template literals instead of string concatenation. + * + * 🔧 Fixable - https://eslint.org/docs/rules/prefer-template + */ + 'prefer-template': 'warn', + /** + * Require a `Symbol` description. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/symbol-description + */ + 'symbol-description': 'error', + + // Stylistic + /** + * Require camel case names. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/camelcase + */ + camelcase: ['error', { allow: ['^UNSAFE_'], ignoreDestructuring: false, properties: 'never' }], + /** + * Require function expressions to have a name. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/func-names + */ + 'func-names': ['error', 'as-needed'], + /** + * Require a capital letter for constructors. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/new-cap + */ + 'new-cap': ['error', { capIsNew: false }], + /** + * Disallow the omission of parentheses when invoking a constructor with + * no arguments. + * + * 🔧 Fixable - https://eslint.org/docs/rules/new-parens + */ + 'new-parens': 'warn', + /** + * Disallow use of the Array constructor. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-array-constructor + */ + 'no-array-constructor': 'error', + /** + * Disallow use of bitwise operators. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-bitwise + */ + 'no-bitwise': 'error', + /** + * Disallow if as the only statement in an else block. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-lonely-if + */ + 'no-lonely-if': 'warn', + /** + * Disallow use of chained assignment expressions. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-multi-assign + */ + 'no-multi-assign': ['error'], + /** + * Disallow nested ternary expressions. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-nested-ternary + */ + 'no-nested-ternary': 'error', + /** + * Disallow ternary operators when simpler alternatives exist. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-unneeded-ternary + */ + 'no-unneeded-ternary': 'error', + /** + * Require use of an object spread over Object.assign. + * + * 🔧 Fixable - https://eslint.org/docs/rules/prefer-object-spread + */ + 'prefer-object-spread': 'warn', + + // Variables + /** + * Require one variable declaration per scope. + * Require consecutive let declarations to be a single declaration. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/one-var + */ + 'one-var': ['error', { let: 'consecutive' }], + /** + * Disallow labels that share a name with a variable. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-label-var + */ + 'no-label-var': 'error', + /** + * Disallow initializing variables to `undefined`. + * + * 🔧 Fixable - https://eslint.org/docs/rules/no-undef-init + */ + 'no-undef-init': 'warn', + /** + * Disallow unused variables. + * + * 🚫 Not fixable - https://eslint.org/docs/rules/no-unused-vars + */ 'no-unused-vars': [ + 'error', + { + args: 'after-used', + argsIgnorePattern: '^_', + ignoreRestSiblings: false, + vars: 'all', + varsIgnorePattern: '^_', + }, + ], + + // Audiolab preferences + }, + }, +] diff --git a/eslint/configs/json.js b/eslint/configs/json.js new file mode 100644 index 0000000..182c1b8 --- /dev/null +++ b/eslint/configs/json.js @@ -0,0 +1,4 @@ +import { pluginJsonc } from '../plugins.js' + +/** @type {import("eslint-define-config").FlatESLintConfigItem[]} */ +export const json = [...pluginJsonc.configs['flat/recommended-with-json']] diff --git a/eslint/configs/node.js b/eslint/configs/node.js new file mode 100644 index 0000000..45a774e --- /dev/null +++ b/eslint/configs/node.js @@ -0,0 +1,18 @@ +import { pluginNode } from '../plugins.js' + +export const node = [ + pluginNode.configs['flat/recommended-script'], + { + rules: { + 'n/handle-callback-err': ['error', '^(err|error)$'], + 'n/no-deprecated-api': 'error', + 'n/no-exports-assign': 'error', + 'n/no-new-require': 'error', + 'n/no-path-concat': 'error', + 'n/no-unsupported-features/es-builtins': 'error', + 'n/prefer-global/buffer': ['error', 'never'], + 'n/prefer-global/process': ['error', 'never'], + 'n/process-exit-as-throw': 'error', + }, + }, +] diff --git a/eslint/configs/perfectionist.js b/eslint/configs/perfectionist.js new file mode 100644 index 0000000..f4263b2 --- /dev/null +++ b/eslint/configs/perfectionist.js @@ -0,0 +1,10 @@ +import { pluginPerfectionist } from '../plugins.js' + +/** @type {import("eslint-define-config").FlatESLintConfigItem[]} */ +export const perfectionist = [ + { + plugins: { + perfectionist: pluginPerfectionist, + }, + }, +] diff --git a/eslint/configs/prettier.js b/eslint/configs/prettier.js new file mode 100644 index 0000000..31450c5 --- /dev/null +++ b/eslint/configs/prettier.js @@ -0,0 +1,27 @@ +import { configPrettier, pluginPrettier } from '../plugins.js' + +const prettierConflictRules = { ...configPrettier.rules } +delete prettierConflictRules['vue/html-self-closing'] + +/** @type {import('eslint-define-config').FlatESLintConfigItem[]} */ +export const prettier = [ + // Any other config imports go at the top + // pluginPrettierRecommended, + { + // ignores: ['package.json'], + plugins: { + prettier: pluginPrettier, + }, + rules: { + ...prettierConflictRules, + ...pluginPrettier.configs.recommended.rules, + 'prettier/prettier': [ + 'warn', + { + // printWidth: 120, + useTabs: true, + }, + ], + }, + }, +] diff --git a/eslint/configs/security.js b/eslint/configs/security.js new file mode 100644 index 0000000..fb94387 --- /dev/null +++ b/eslint/configs/security.js @@ -0,0 +1,6 @@ +import { pluginSecurity } from '../plugins.js' + +/** @type {import("eslint-define-config").FlatESLintConfigItem[]} */ +// TODO: waiting for https://github.com/eslint-community/eslint-plugin-security/pull/145 +// to be merged +export const security = [pluginSecurity.configs.recommended] diff --git a/eslint/configs/sonarjs.js b/eslint/configs/sonarjs.js new file mode 100644 index 0000000..d6ffa17 --- /dev/null +++ b/eslint/configs/sonarjs.js @@ -0,0 +1,15 @@ +import { pluginSonarJS } from '../plugins.js' + +/** @type {import("eslint-define-config").FlatESLintConfigItem[]} */ +export const sonarjs = [ + { + plugins: { + sonarjs: pluginSonarJS, + }, + rules: { + // does not yet work with ESlint Flat Config + // ...pluginSonarJS.configs.recommended.rules, + 'sonarjs/cognitive-complexity': ['error', 40], + }, + }, +] diff --git a/eslint/configs/sort.js b/eslint/configs/sort.js new file mode 100644 index 0000000..445cb6e --- /dev/null +++ b/eslint/configs/sort.js @@ -0,0 +1,76 @@ +export const sortPackageJson = [ + { + files: ['**/package.json'], + rules: { + 'jsonc/sort-array-values': [ + 'error', + { + order: { type: 'asc' }, + pathPattern: '^files$', + }, + ], + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'name', + 'version', + 'private', + 'packageManager', + 'description', + 'type', + 'keywords', + 'license', + 'homepage', + 'bugs', + 'repository', + 'author', + 'contributors', + 'funding', + 'files', + 'main', + 'module', + 'types', + 'exports', + 'typesVersions', + 'sideEffects', + 'unpkg', + 'jsdelivr', + 'browser', + 'bin', + 'man', + 'directories', + 'publishConfig', + 'scripts', + 'peerDependencies', + 'peerDependenciesMeta', + 'optionalDependencies', + 'dependencies', + 'devDependencies', + 'engines', + 'config', + 'overrides', + 'pnpm', + 'husky', + 'lint-staged', + 'eslintConfig', + 'prettier', + ], + pathPattern: '^$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$', + }, + { + order: ['types', 'require', 'import', 'default'], + pathPattern: '^exports.*$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$', + }, + ], + }, + }, +] diff --git a/eslint/configs/unicorn.js b/eslint/configs/unicorn.js new file mode 100644 index 0000000..3dd21aa --- /dev/null +++ b/eslint/configs/unicorn.js @@ -0,0 +1,29 @@ +import { pluginUnicorn } from '../plugins.js' + +export const unicorn = [ + { + plugins: { + unicorn: pluginUnicorn, + }, + rules: { + /** + * Require consistent filename case for all linted files. + * Disabled for now. + * + * 🚫 Not fixable - https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md + */ + // 'unicorn/filename-case': [ + // 'error', + // { + // case: 'kebabCase', + // }, + // ], + /** + * Require using the `node:` protocol when importing Node.js built-in modules. + * + * 🔧 Fixable - https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-node-protocol.md + */ + 'unicorn/prefer-node-protocol': 'warn', + }, + }, +] diff --git a/eslint/configs/vue.js b/eslint/configs/vue.js new file mode 100644 index 0000000..ba34aeb --- /dev/null +++ b/eslint/configs/vue.js @@ -0,0 +1,4 @@ +import { pluginVue } from '../plugins.js' + +/** @type {import("eslint-define-config").FlatESLintConfigItem[]} */ +export const vue = [...pluginVue.configs['flat/essential']] diff --git a/eslint/constants.js b/eslint/constants.js new file mode 100644 index 0000000..bf26281 --- /dev/null +++ b/eslint/constants.js @@ -0,0 +1,25 @@ +export const ECMA_VERSION = 2022 +export const GLOB_SRC_EXT = '?([cm])[jt]s?(x)' +export const GLOB_JS = '**/*.?([cm])js' +export const JAVASCRIPT_FILES = ['*.js?(x)', '*.mjs'] +export const TYPESCRIPT_FILES = ['*.ts?(x)'] +export const GLOB_EXCLUDE = [ + '**/node_modules', + '**/dist', + '**/package-lock.json', + '**/yarn.lock', + '**/pnpm-lock.yaml', + '**/bun.lockb', + + '**/coverage', + '**/.idea', + '**/.output', + '**/.vite-inspect', + '**/.nitro', + + '**/*.min.*', + + 'CHANGELOG*.md', + '**/CHANGELOG*.md', +] +export const GLOB_JSON = '**/*.json' diff --git a/eslint/index.js b/eslint/index.js new file mode 100644 index 0000000..5975393 --- /dev/null +++ b/eslint/index.js @@ -0,0 +1,5 @@ +export * from './configs/index.js' +export * from './presets.js' +// TODO: handle constants +export * from './constants.js' +export * from './plugins.js' diff --git a/eslint/plugins.js b/eslint/plugins.js new file mode 100644 index 0000000..9acd356 --- /dev/null +++ b/eslint/plugins.js @@ -0,0 +1,29 @@ +export { default as pluginJs } from '@eslint/js' +export { default as pluginComments } from 'eslint-plugin-eslint-comments' +// TODO: Are there benefits using eslint-plugin-import? +export * as pluginImport from 'eslint-plugin-import-x' +export { default as pluginNode } from 'eslint-plugin-n' +export { default as pluginUnicorn } from 'eslint-plugin-unicorn' +export { default as pluginJsonc } from 'eslint-plugin-jsonc' + +export * as pluginSonarJS from 'eslint-plugin-sonarjs' + +// required ts-eslint (parser) +// export { default as pluginUnusedImports } from 'eslint-plugin-unused-imports' +// export * as pluginUnusedImports from 'eslint-plugin-unused-imports' + +export { default as pluginPerfectionist } from 'eslint-plugin-perfectionist' + +export { default as pluginVue } from 'eslint-plugin-vue' + +// import tseslint from 'typescript-eslint' +// export { tseslint } + +// TODO: remove unused recommended config +// export { default as pluginPrettierRecommended } from 'eslint-plugin-prettier/recommended' +export { default as pluginPrettier } from 'eslint-plugin-prettier' +export * as configPrettier from 'eslint-config-prettier' + +export { default as pluginSecurity } from 'eslint-plugin-security' + +// export * as parserVue from 'vue-eslint-parser' diff --git a/eslint/presets.js b/eslint/presets.js new file mode 100644 index 0000000..176966c --- /dev/null +++ b/eslint/presets.js @@ -0,0 +1,72 @@ +import { + ignores, + comments, + errors, + imports, + javascript, + node, + prettier, + json, + perfectionist, + sortPackageJson, + unicorn, + vue, + sonarjs, +} from './configs/index.js' + +/** + * @typedef {import('eslint-define-config').FlatESLintConfigItem} FlatESLintConfigItem + */ + +/** Ignore common files and include javascript support */ +export const presetJavaScript = [ + ...ignores, + ...javascript, + ...comments, + ...imports, + ...errors, + ...unicorn, + ...node, + ...sonarjs, +] +/** Includes `presetJavaScript` and typescript support */ +export const presetBasic = [...presetJavaScript, ...json, ...perfectionist, ...sortPackageJson] +export const presetAll = [...presetBasic, ...vue, ...prettier] +export { presetBasic as basic, presetAll as all } + +const migrationOverwrites = [ + { + rules: { + 'eslint-comments/require-description': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/prefer-node-protocol': 'off', + 'import/order': 'off', + }, + }, +] + +/** + * + * @param {FlatESLintConfigItem | FlatESLintConfigItem[]} config + * @param {{prettier: boolean, vue: boolean, cjs: boolean, migrate: boolean}} features + * @returns {FlatESLintConfigItem[]} + */ +export function audiolab( + config = [], + { prettier: enablePrettier = true, vue: enableVue = true, migrate = false } = {} +) { + const configs = [...presetBasic] + if (enableVue) { + configs.push(...vue) + } + if (enablePrettier) { + configs.push(...prettier) + } + if (Object.keys(config).length > 0) { + configs.push(...(Array.isArray(config) ? config : [config])) + } + if (migrate) { + configs.push(...migrationOverwrites) + } + return configs +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3c7a5c1 --- /dev/null +++ b/package.json @@ -0,0 +1,82 @@ +{ + "name": "@swrlab/style-guide", + "version": "1.0.0", + "packageManager": "yarn@4.1.1", + "description": "SWR Audio Lab's engineering style guide", + "type": "module", + "license": "ISC", + "homepage": "https://github.com/swrlab/style-guide#readme", + "bugs": { + "url": "https://github.com/swrlab/style-guide/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/swrlab/style-guide.git" + }, + "files": [ + "biome", + "eslint", + "prettier" + ], + "main": "index.js", + "exports": { + "./biome": "./biome/biome.json", + "./eslint/*": "./eslint/*.js", + "./prettier": "./prettier/index.js" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "lint": "eslint --max-warnings=0 .", + "lint:fix": "eslint --max-warnings=0 --fix .", + "prettier-check": "prettier --check .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "peerDependencies": { + "eslint": ">=8.48.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "prettier": { + "optional": true + } + }, + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/eslint-parser": "^7.24.7", + "@eslint/js": "^9.6.0", + "@stylistic/eslint-plugin-js": "^1.7.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-import-x": "^0.5.2", + "eslint-plugin-jsonc": "^2.16.0", + "eslint-plugin-n": "^17.9.0", + "eslint-plugin-perfectionist": "^2.11.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-security": "^2.1.1", + "eslint-plugin-sonarjs": "^0.25.1", + "eslint-plugin-testing-library": "^6.2.2", + "eslint-plugin-unicorn": "^52.0.0", + "eslint-plugin-unused-imports": "^3.1.0", + "eslint-plugin-vue": "^9.26.0", + "globals": "^15.7.0" + }, + "devDependencies": { + "eslint": "^9.6.0", + "eslint-define-config": "^2.1.0", + "lint-staged": "^15.2.7", + "prettier": "^3.3.2" + }, + "engines": { + "node": ">=18" + }, + "lint-staged": { + "*": "prettier -w --ignore-unknown" + }, + "prettier": "./prettier/index.js" +} diff --git a/prettier/index.js b/prettier/index.js new file mode 100644 index 0000000..b335640 --- /dev/null +++ b/prettier/index.js @@ -0,0 +1,40 @@ +/** + * Some of Prettier's defaults can be overridden by an EditorConfig file. + * We define those here to ensure that doesn't happen. + * + * See: https://github.com/prettier/prettier/blob/main/docs/configuration.md#editorconfig + */ +const overridableDefaults = { + endOfLine: 'lf', + // tabWidth is neither set in .editorconfig nor here, so every developer + // can set their own preference in their editor locally. + // tabWidth: 8, + // default is 80 + // same goes with `printWidth`. We inherit this value from the local editorconfig. + // printWidth: 120, + useTabs: true, + semi: false, + trailingComma: 'es5', +} + +/** + * @type {import('prettier').Config} + * @see https://prettier.io/docs/en/options.html + */ +export const config = { + ...overridableDefaults, + singleQuote: true, + // Conflicts with sortPackagejson (from sort.js) + // plugins: ['prettier-plugin-packagejson'], + overrides: [ + { + files: ['*.yml', '*.yaml', '*.md'], + options: { + useTabs: false, + tabWidth: 2, + }, + }, + ], +} + +export default config