Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom eslint configuration #77

Merged
merged 15 commits into from
Dec 5, 2024
Merged
165 changes: 118 additions & 47 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Airbnb Config: https://www.npmjs.com/package/eslint-config-airbnb
# Airbnb Config for Typescript: https://www.npmjs.com/package/eslint-config-airbnb-typescript
extends:
- airbnb
- airbnb-typescript
- eslint:recommended
- plugin:@typescript-eslint/strict-type-checked
- plugin:@typescript-eslint/stylistic-type-checked
- plugin:react/recommended
- plugin:react/jsx-runtime
- plugin:react-hooks/recommended
- plugin:jsx-a11y/recommended
- prettier
- "plugin:deprecation/recommended"
- plugin:deprecation/recommended
parser: "@typescript-eslint/parser"
parserOptions:
ecmaVersion: 2018
Expand All @@ -13,65 +16,133 @@ parserOptions:
jsx: true
warnOnUnsupportedTypeScriptVersion: true
project: "./tsconfig.json"
projectService: true
tsconfigRootDir: "."
env:
browser: true
plugins:
- testing-library
- "@typescript-eslint"
- react
- react-hooks
- jsx-a11y
- "check-file"
ignorePatterns:
- build/**/*
rules:
# This rule has been deprecated, see https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md
jsx-a11y/label-has-for: "off"
jsx-a11y/label-has-associated-control:
- error
- assert: either
# When using methods with underscores inside a class, you need to enable this rule
no-underscore-dangle:
- error
- allowAfterThis: true
# We prefer not enforcing this. It's not generally a good idea to
# name files containing JSX with a non-official extension.
react/jsx-filename-extension: "off"
linebreak-style: 0
# Sometimes it's more convenient to have named exports even
# though only one thing has to be exported.
import/prefer-default-export: "off"
# Destructuring sometimes makes code harder to read.
react/destructuring-assignment: "off"
# For Typescript this is needed since it can call out false negatives.
# https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md#how-to-use
# https://github.com/typescript-eslint/typescript-eslint/issues/2540#issuecomment-692505191
no-use-before-define: "off"
react/function-component-definition:
- error
- namedComponents: arrow-function
unnamedComponents: arrow-function
react/react-in-jsx-scope: "off"
react/prop-types: "off"
react/require-default-props: "off"
react/jsx-props-no-spreading: "off"
react/button-has-type: "off"
"@typescript-eslint/type-annotation-spacing": error
# Eslint Possible Problems
array-callback-return: ["error", { allowImplicit: true }]
no-await-in-loop: error
no-constant-binary-expression: error
no-constructor-return: error
no-duplicate-imports: off # Creates issues with typescript type imports
no-new-native-nonconstructor: error
no-promise-executor-return: error
no-self-compare: error
no-template-curly-in-string: error
no-unmodified-loop-condition: error
no-unreachable-loop: error
no-unused-private-class-members: error
no-use-before-define: off # Checked by typescript-eslint
require-atomic-updates: error
# Eslint Suggestions
arrow-body-style: ["error", "as-needed"]
block-scoped-var: error
camelcase: ["error", { properties: "never", ignoreDestructuring: false }]
consistent-return: off # Checked by typescript-eslint
consistent-this: error
dot-notation: ["error", { allowKeywords: true }]
eqeqeq: error
guard-for-in: error
init-declarations: error # Could cause problems with TS
logical-assignment-operators: error
new-cap: error
no-alert: warn
no-array-constructor: error
no-bitwise: warn
no-caller: error
no-console: warn
no-div-regex: error
no-else-return: error
no-empty-static-block: error
no-eq-null: error
no-eval: error
no-extend-native: error
no-extra-bind: error
no-extra-label: error
no-implicit-coercion: ["error", { allow: ["!!"] }]
no-implicit-globals: error
no-implied-eval: error
no-invalid-this: off # Checked by typescript-eslint
no-iterator: error
no-label-var: error
no-labels: error
no-lone-blocks: error
no-lonely-if: error
no-loop-func: off # Checked by typescript-eslint
no-multi-assign: error
no-new: error
no-new-func: error
no-new-wrappers: error
no-object-constructor: error
no-param-reassign: error
no-proto: error
no-return-assign: error
no-script-url: error
no-sequences: error
no-shadow: off # Checked by typescript-eslint
no-throw-literal: error
no-unneeded-ternary: error
no-unused-expressions: error
no-useless-call: error
no-useless-computed-key: error
no-useless-concat: error
no-useless-constructor: error
no-useless-rename: error
no-useless-return: error
no-var: error
no-void: error
no-with: error
object-shorthand: error
prefer-arrow-callback: error
prefer-const: error
prefer-exponentiation-operator: error
prefer-numeric-literals: error
prefer-promise-reject-errors: error
prefer-regex-literals: error
prefer-template: error
radix: error
require-await: error
require-yield: error
yoda: error
# --------- Typescript Eslint ----------
"@typescript-eslint/consistent-return": error
"@typescript-eslint/consistent-type-exports": error
"@typescript-eslint/consistent-type-imports": error
"@typescript-eslint/default-param-last": error
"@typescript-eslint/member-ordering": error
"@typescript-eslint/no-confusing-void-expression": error
"@typescript-eslint/no-empty-interface": error
"@typescript-eslint/no-invalid-this": error
"@typescript-eslint/no-loop-func": error
"@typescript-eslint/no-shadow": error
"@typescript-eslint/no-use-before-define": error
"@typescript-eslint/no-useless-empty-export": error
"@typescript-eslint/no-var-requires": error
# --------- Other Plugins ----------
check-file/filename-naming-convention:
- error
- "**/*.{jsx,tsx,ts,js,css,sass,svg,jpg,jpeg,png}": "KEBAB_CASE"
- "**/*.{jsx,tsx,ts,js}": "KEBAB_CASE"
- ignoreMiddleExtensions: true
check-file/folder-naming-convention:
- error
- "src/**/": "KEBAB_CASE"
import/no-extraneous-dependencies:
- error
- devDependencies:
- "{vite,vitest}.config.*"
- "teardown.js"
- "**/vite-env.d.ts"
- "**/tests/**"
- "**/*.stories.tsx"
- "**/*.test.{ts,tsx}"
react/prop-types: off

# Allows for absolute imports
settings:
react:
version: "detect"
import/resolver:
node:
paths: ["src"]
3 changes: 3 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
node-version: 22.8.0
cache: npm

- name: Update NPM
run: npm i -g [email protected]

- name: Install dependencies
run: npm ci

Expand Down
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
2 changes: 1 addition & 1 deletion .woodpecker/.frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pipeline:
setup:
<<: *common
commands:
- npm i -g npm@10.8.2
- npm i -g npm@10.9.0
- npm ci --prefer-offline --engine-strict
js-prettier:
<<: *common
Expand Down
74 changes: 28 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
# Xmartlabs Create React Boilerplate

![react version](https://img.shields.io/badge/react-18.2.0-brightgreen)
![react-dom version](https://img.shields.io/badge/react--dom-18.2.0-brightgreen)
![react-router-dom version](https://img.shields.io/badge/react--router--dom-5.3.4-brightgreen)
![vite version](https://img.shields.io/badge/vite-4.1.4-brightgreen)
![history version](https://img.shields.io/badge/history-4.10.1-brightgreen)
![sass version](https://img.shields.io/badge/sass-1.58.3-brightgreen)
![vitest version](https://img.shields.io/badge/vitest-0.29.2-brightgreen)
![axios version](https://img.shields.io/badge/axios-0.21.4-brightgreen)
![eslint version](https://img.shields.io/badge/eslint-8.10.0-brightgreen)

## Contributing to this Boilerplate

Make sure you have the appropriate version of Node (22.8.0) and NPM (10.8.2) installed.
Make sure you have the appropriate version of Node (22.8.0) and NPM (10.9.0) installed.

Then install the required packages:

Expand Down Expand Up @@ -206,32 +196,32 @@ All networking calls must be made in controllers. They are in charge of knowing

```ts
// src/networking/controllers/example-controller.ts
import { ExampleSerializer } from "networking/serializers/example-serializer";
import {
serialize,
deSerialize,
} from "networking/serializers/example-serializer";
import { ApiService } from "networking/api-service";
import { API_ROUTES } from "networking/api-routes";

class ExampleController {
static async getExamples() {
const response = await ApiService.get<RawExample[]>(API_ROUTES.EXAMPLE);
return (response.data || []).map(ExampleSerializer.deSerialize);
}

static createExample(example: Example) {
const serializedExample = ExampleSerializer.serialize(example);
return ApiService.post(API_ROUTES.EXAMPLE, {
example: serializedExample,
});
}
}
export const getExamples = async (): Promise<Example[]> => {
const response = await ApiService.get<RawExample[]>(API_ROUTES.EXAMPLE);
return response.map<Example>(deSerialize);
};

export { ExampleController };
export const createExample = async (example: Example): Promise<Example> => {
const serializedExample = serialize(example);
const response = await ApiService.post<RawExample>(API_ROUTES.EXAMPLE, {
body: JSON.stringify(serializedExample),
});
return deSerialize(response);
};
```

This controller has two methods: `getExamples` and `createExamples`. The first method attempts to get a list of examples from the backend. Once the data arrives the controller will attempt to deserialize the data via the `ExampleSerializer` and then create instances of `Example` from each.
This controller has two methods: `getExamples` and `createExample`. The first method attempts to get a list of examples from the backend. Once the data arrives the controller will attempt to deserialize the data via the methods defined on the serializer file.

The second method illustrates how we would go about _sending_ data to the backend. In this case we do the inverse process as before: given an instance of `Example` the controller will attempt to serialize (as opposed to deserialize) it and send it as payload.

Let's look at how exactly serializers work.
Let's look at how serializers work.

### Serializers

Expand All @@ -241,26 +231,18 @@ The advantage of this is that you can redefine the fields of the data and remove

```ts
// src/networking/serializers/example-serializer.ts
class ExampleSerializer {
static deSerialize(data: RawExample): Example {
return {
foo: data.Foobaz,
bar: data.Barbaz,
};
}

static serialize(example: Example): RawExample {
return {
Foobaz: example.foo,
Barbaz: example.bar,
};
}
}

export { ExampleSerializer };
export const deSerialize = (data: RawExample): Example => ({
foo: data.Foobaz,
bar: data.Barbaz,
});

export const serialize = (example: Example): RawExample => ({
Foobaz: example.foo,
Barbaz: example.bar,
});
```

When deserializing in this (admittedly simple) example the `ExampleSerializer` receives an instance of `RawExample` and returns an instance of `SerializedExample`. In this case we're simplifying the keys of the JSON we've received, by removing the common suffix `baz`. When serializing we're restoring the example to the format the API will understand.
When deserializing in this (admittedly simple) example the `deSerialize` method receives an instance of `RawExample` and returns an instance of `Example`. In this case we're simplifying the keys of the JSON we've received, by removing the common suffix `baz`. When serializing we're restoring the example to the format the API will understand.

#### Types

Expand Down
Loading