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

What rules do the community tend to override? #1089

Open
ljharb opened this issue Sep 25, 2016 · 151 comments
Open

What rules do the community tend to override? #1089

ljharb opened this issue Sep 25, 2016 · 151 comments
Labels

Comments

@ljharb
Copy link
Collaborator

ljharb commented Sep 25, 2016

I'm opening this issue specifically to gather a list of the rules that those in the community feel are controversial or problematic, such that they override them, and most importantly, why.

This is not an invitation for "+1s" (those belong as reactions on individual posts), or an invitation to hear about everyone's subjective aesthetic preferences, nor is it an indication that any of these rules necessarily will - or will not - change. I'm simply hoping to primarily gather civil, well-reasoned explanations for why certain rules are overridden, disabled, etc.

Any comments mentioning spaces vs tabs, or semicolons, will be summarily deleted :-)

We're aware of the following:

  • react/jsx-filename-extension - filed as Proposal: [react][breaking] Only .js files should contain JSX. #985. Why is it controversial? Some believe that .js files should be allowed to contain JSX. Others believe this is an app-level concern, and does not belong in a shared config (like "env" settings).
  • import/no-extraneous-dependencies: in test directories, for example, it needs to be overridden to the following (note: if/when eslint allows glob-based config, then we'd set up some common test directory patterns so you wouldn't have to do this manually) eslint-config-airbnb v13.0.0 is now released, which resolves this.
  "import/no-extraneous-dependencies": ["error", {
    "devDependencies": true,
    "optionalDependencies": false,
  }],
  • camelcase: when you're bound to the casing selected by APIs (What rules do the community tend to override? #1089 (comment), but Airbnb suffers from this too) This might also apply to new-cap and no-underscore-dangle.
  • no-param-reassign: this happens frequently with a reduce where the entire operation is pure, but the reducer is not (ie, it mutates the accumulator, but the accumulator began as {} passed into the reducer). You can use Object.assign({}, accumulator, changes), or { ...accumulator, ...changes }, however. (per What rules do the community tend to override? #1089 (comment))
  • no-use-before-define: some enjoy using hoisting to define helper methods at the bottom of the file. This guide discourages relying on hoisting; instead suggesting importing the helpers from another file when possible. (per What rules do the community tend to override? #1089 (comment))
  • no-mixed-operators - specifically where it relates to arithmetic operators, where the PEMDAS rule applies. We're definitely considering loosening this rule as it applies to *, /, +, and -.

Any others? I'll update the original post with new examples as I'm made aware of them.

(Note: this does not cover env settings, like "browser", "node", "mocha", etc - these are app-level concerns, and as such, your .eslintrc, tests/.eslintrc, etc should be defining them)

@jonathanong
Copy link

jonathanong commented Sep 25, 2016

  • arrow-body-style or whatever, because it makes converting regular functions to arrow functions difficult as it's not fixed automatically.
  • camelcase - because many times i'm bound by other APIs
  • no-param-reassign - makes working with koa/express/node.js difficult
  • no-plusplus - because i += 1 is annoying
  • prefer-template - because it isn't convenient 5% of the time
  • consistent-return - annoying with early returns
  • new-cap - bound by other APIs
  • no-underscore-dangle - disallows "private" properties. bound by mongodb.
  • no-use-before-define - disallows function declarations at the bottom

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 25, 2016

@jonathanong Thanks! I'd forgotten camelcase, we have that problem too. Re arrow-body-style, would the existence of an autofixer make it not be a problem?

Can you elaborate on when prefer-template isn't convenient?

@jonathanong
Copy link

jonathanong commented Sep 25, 2016

arrow-body-style - not sure, because sometimes i'd prefer having the entire { return x } block when the function is multi-line so that it's easy to add logic before the return in the future.

when i have something like path + '?' + query.

`${path}?${query}`

just seems like more work

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 25, 2016

Thanks - I've added a few of your examples to the OP. (and fixed your markdown, hope you don't mind)

@chadwatson
Copy link

chadwatson commented Sep 25, 2016

no-use-before-define for functions. I personally don't like to scroll down to the bottom of a file to see what a module really does. It's inevitable that for some modules it just makes sense to leave the helper functions in the same file. I prefer to see the declarative stuff first instead of having to scroll through the imperative stuff first.

@steida
Copy link

steida commented Sep 25, 2016

https://github.com/este/este/blob/master/.eslintrc

// Soft some rules.
"global-require": 0, // Used by React Native.
"new-cap": [2, {"capIsNew": false, "newIsCap": true}], // For immutable Record() etc.
"no-class-assign": 0, // Class assign is used for higher order components.
"no-nested-ternary": 0, // It's nice for JSX.
"no-param-reassign": 0, // We love param reassignment. Naming is hard.
"no-shadow": 0, // Shadowing is a nice language feature. Naming is hard.
"import/imports-first": 0, // Este sorts by atom/sort-lines natural order.
"react/jsx-filename-extension": 0, // No, JSX belongs to .js files
"jsx-a11y/html-has-lang": 0, // Can't recognize the Helmet.
"no-confusing-arrow": 0, // This rule is super confusing.
"react/forbid-prop-types": 0, // Este is going to use Flow types.
"react/no-unused-prop-types": 0, // Este is going to use Flow types.
"class-methods-use-this": 0, // Good idea, but ignores React render.
"arrow-parens": 0, // Not really.

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 25, 2016

@steida Thanks!

Can you give me an example of your no-class-assign usage?

Also, fwiw, new-cap has capIsNewExceptions and newIsCapExceptions so you could define things like Record etc without having to alter the individual booleans.

Why would you need to disable forbid-prop-types or no-unused-prop-types if you're using Flow? It seems like they'd just be a noop if you don't have any propTypes defined.

As for class-methods-use-this, I added an exceptMethods option to the eslint rule, so the next version of the config will be excluding all the React lifecycle methods (including render), so you'll be able to remove that override soon. (update v12 of the config now handles this)

@steida
Copy link

steida commented Sep 25, 2016

As for no-class-assign
https://github.com/este/este/blob/5571b8ab6722d5abd75984859292f5a5258c7151/src/browser/auth/Email.js#L155

As for forbid-prop-types and no-unused-prop-types
I remember I had to set it, why? I don't remember.

As for the rest.
Thank you.

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 25, 2016

@steida thanks - for the prop types ones, please file bugs on eslint-plugin-react if you find you still need the overrides. for the no-class-assign one, it seems like you could do const InnerEmail = class Email { … } and then const FocusedEmail = focus(InnerEmail, 'focus'); etc?

@ljharb ljharb changed the title What rules does the community tend to override? What rules do the community tend to override? Sep 25, 2016
@jonase
Copy link
Contributor

jonase commented Sep 25, 2016

The only custom rule we're using currently is

"new-cap": [2, {"capIsNewExceptions": ["Immutable.Map", "Immutable.Set", "Immutable.List"]}]

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 25, 2016

@jonase I think those are common and legit enough that if you'd like to make a PR to the base config, I'd be happy to add them.

@oliviertassinari
Copy link
Contributor

From Material-UI:

'no-console': 'error', // airbnb is using warn
'no-return-assign': 'off', // airbnb use error, handy for react ref assign.
'operator-linebreak': ['error', 'after'], // aibnb is disabling this rule

'react/jsx-handler-names': ['error', { // airbnb is disabling this rule
  eventHandlerPrefix: 'handle',
  eventHandlerPropPrefix: 'on',
}],
'react/jsx-filename-extension': ['error', {extensions: ['.js']}], // airbnb is using .jsx
'react/jsx-max-props-per-line': ['error', {maximum: 3}], // airbnb is disabling this rule
'react/no-danger': 'error', // airbnb is using warn
'react/no-direct-mutation-state': 'error', // airbnb is disabling this rule

@bebraw
Copy link

bebraw commented Sep 25, 2016

I have set the following at Reactabular:

"rules": {
  "comma-dangle": ["error", "never"], // personal preference
  "prefer-arrow-callback": 0, // mocha tests (recommendation)
  "func-names": 0, // mocha tests (recommendation)
  "import/no-extraneous-dependencies": 0, // monorepo setup
  "import/no-unresolved": [1, { ignore: ['^reactabular'] }], // monorepo setup
  "no-underscore-dangle": 0, // implementation detail (_highlights etc.)
  "no-unused-expressions": 0, // chai
  "no-use-before-define": 0, // personal preference
  "react/sort-comp": 0, // personal preference
  "react/no-multi-comp": 0 // personal preference
}

@sompylasar
Copy link

sompylasar commented Sep 25, 2016

"no-nested-ternary": 0,  // because JSX
"no-underscore-dangle": 0,  // because private properties
"arrow-body-style": 0,  // because cannot enforce what's personally preferred: 
  // (x) => ( some ? ( comple + x ) : ( e + x + pression ) )
  // (x) => some.trivialExpression()
  // (x) => { return ( <JSX /> ); }  // to add something later without re-indenting.

@broguinn
Copy link

At my company, we've overridden some rules that don't play nice with Mocha:

  • arrow-body-style means before hooks can't access the all-important this
  • no-unused-expressions makes expect(true).to.be.true; to cause a linting error

Also:

  • prefer-rest-params is not supported in Node 4.x

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 25, 2016

@oliviertassinari Thanks! Most of yours are strengthening rules we don't have on/as errors. For ref assign, ref callbacks don't have a return value, so instead of ref => this.ref = ref, the guide recommends you use (ref) => { this.ref = ref; } which doesn't collide with the rule.

@bebraw re no-underscore-dangle, JS doesn't have private properties :-/ hopefully you've read the recent guide section on them. re no-unused-expressions, see below.

@broguinn arrow-body-style in mocha tests does do that, but i've found that using this in mocha tests is an antipattern, and an unfortunate API choice of mocha itself. For the few times we need it, we just use a normal function and give it a name. Re expect(true).to.be.true, that's a hugely problematic pattern, because expect(true).to.be.yogurt passes too - it's a noop. We recommend expect(true).to.equal(true) or expect(!!truthy).to.equal(true); which can't silently fail. Re prefer-rest-params, the guide does recommend/assume you are using babel.

To all: regarding no-nested-ternary, I understand why a single ternary statement is useful in JSX. Can someone provide me a code example of why a nested ternary is useful in JSX?

@relekang
Copy link

The interesting changes from our config is that we changed these to be able to use flowtype in a good way:

    "no-duplicate-imports": [0], // Does not support import of types from same file/module as is already used.
    "import/no-duplicates": [2], 
    "react/sort-comp": [2, {
      "order": [
        "type-annotations",  // sort type annotations on the top
        "static-methods",
        "lifecycle",
        "/^on.+$/",
        "/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/",
        "everything-else",
        "/^render.+$/",
        "render",
      ],
    }],

You can find the config here. The other changes are mostly adding extra rules and disabling a few because we haven't always used airbnb as a base.

@bebraw
Copy link

bebraw commented Sep 26, 2016

@ljharb Yeah, I'm not using _ as private. It's a special case (convention) where I use it to signify a field with special meaning (i.e. something generated). I wouldn't go as far as to remove the rule from this preset.

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 26, 2016

@relekang Thanks! re no-duplicate-imports, does Flow not support importing multiple things all in one import statement? import/no-duplicates has been enabled in our config since May, so you can safely remove it.

@relekang
Copy link

relekang commented Sep 26, 2016

@ljharb I am not sure, but I have not found a way to combine

import {someFunction} from 'module';

and

import type {someType, someOtherType} from 'module';

into one line.

import/no-duplicates has been enabled in our config since May, so you can safely remove it.

Nice 👌 Need to read the changelog closer.

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 26, 2016

@relekang if that's true, i'd file that as a bug on flow :-)

@devillecodes
Copy link

Thanks for soliciting community feedback @ljharb!

We override the following rules (that fall outside of the aesthetic preference category):

  • require-yield: 0 - Working with Koa.js, there are some cases where we don't yield from a generator.
  • strict: [0, "global"] - We work mostly with ES6 modules, which are strict already.

@lelandrichardson
Copy link

i'd argue that accepting duplicate import paths when one is import type and another is just import should probably be filed as a corner case with the import eslint plugin. Seems like a valid use case to me.

@lencioni
Copy link
Member

It looks like there was an issue to support import type import-js/eslint-plugin-import#225 which has been fixed import-js/eslint-plugin-import#334. The docs for the rule claim to support this, too, so I think you should be good to go!

There is an open issue about allowing exporting types from import/prefer-default-export: import-js/eslint-plugin-import#484

@relekang
Copy link

@ljharb @lelandrichardson @lencioni I did not mean to say that the rule from the import plugin did not work. I tried to say that the rule no-duplicate-imports from eslint itself gives what I would think of as false positives with flowtype imports. Is there a reason that both are in use here? It looks like the one from the import plugin covers the cases that the one from eslint does.

no-duplicate-imports is turned on in es6.js

@ljharb
Copy link
Collaborator Author

ljharb commented Sep 26, 2016

@relekang @lencioni @lelandrichardson since no-duplicate-imports is an eslint core rule, they'd need to support flow in order to detect that corner case. I think it would make more sense for Flow to allow you to combine import types and imports all into one statement.

@ljharb
Copy link
Collaborator Author

ljharb commented Jul 10, 2019

@QuentinRoy node can never be consistent with the browser in this way because the browser has out-of-band metadata via the <script> tag and the HTTP server's MIME type, that node lacks. Desiring that is fine, expecting you'll ever get it is at best naive. Omitting extensions is a best practice because it aids refactoring and avoids having to think about the module format you're importing, which should only be a concern of the author, not the consumer.

@jimmywarting
Copy link

jimmywarting commented Jul 10, 2019

@ljharb

node can never be consistent with the browser in this way because the browser has out-of-band metadata via the <script> tag and the HTTP server's MIME type, that node lacks.

node is trying to become more like a browser, they are looking into adding more browser related stuff into node like never before. ESModules, URL, URLSearchParams, Blob/File, whatwg streams, ppl start using node-fetch and typed array more and more in node just to be consistent with writing cross platform application and not having to include the hole node buffer into the web that takes up lots of scripting.
The fs is becoming more acceptable of TypedArrays now where as it only accepted buffers as arguments before

Omitting extensions is a best practice

Disagree, writing cross node/deno/web applications is a better best practice that outweighs yours

@QuentinRoy
Copy link

QuentinRoy commented Jul 10, 2019

@ljharb I do not think this is the point. Node does not need MIME types to require the use of the full path rather than cropping out the extension.

I agree about the module problem though (I wished they were designed differently in the first place). Hopefully import maps may help solving this. I am not quite sure what is the status of this proposal.

@naseemkullah
Copy link

naseemkullah commented Dec 26, 2019

Not using babel so want to keep strict,
no-use-before-define not applied to functions as having the main function at top with sub-functions below reads better.

{
  "env": {
    "node": true
  },
  "extends": "airbnb-base",
  "parserOptions": {
    "sourceType": "script"
  },
  "rules": {
    "strict": ["error", "global"],
    "no-use-before-define": ["error", "nofunc"]
  }
}

@yairEO
Copy link

yairEO commented Jun 4, 2020

no-nested-tern

for nicer JS reduce methods:

const transform = (filters) => filters.reduce((acc, {name, value}) => ({
    ...acc,
    [name] : value instanceof Array
        ? value
        : value ? [ value ] : []
}), {});

react-hooks/exhaustive-deps

Because I want total control over React useEffect hook dependency array. Many times I intentionally want to leave it an empty Array while calling methods inside it which does something other data from props and I really don't want them to fire if any data changes, but only fire once.

@ljharb
Copy link
Collaborator Author

ljharb commented Jun 4, 2020

@yairEO unrelated, but you want Array.isArray(value) and never instanceof; separately, [name]: [].concat(value || []) avoids the need for that ternary entirely.

@xiaoxiangmoe
Copy link

xiaoxiangmoe commented Jun 5, 2020

We use ternary heavily:

export default function SomeTab() {
  const { data, error } = useSomeRequest();

  return (
    <>
      {error ? (
        <Result
          status="error"
        />
      ) : data === undefined ? (
        <Spin />
      ) : data.length === 0 ? (
        <Empty />
      ) : (
        <div>
          {data.map((log, i) => (
            <div>{log.message}</div>
          ))}
        </div>
      )}
    </>
  );
}

if, switch have fatal flaws —— it is just statement rather than expression. Only expressions can be used as value.

@barraponto
Copy link

I found it hard to use storybook template args (const Template = (args) => <Component {...args} />) and follow react/jsx-props-no-spreading so I just disabled it for storybook files.

@vdh
Copy link
Contributor

vdh commented Sep 14, 2020

@xiaoxiangmoe This is probably just personal preference, but I usually go for either early returns or short-circuiting rather than nested ternaries in those kinds of scenarios.

@xiaoxiangmoe
Copy link

xiaoxiangmoe commented Sep 15, 2020

@vdh We also use ternary in const variable assignment, which can't early returns.

@100terres
Copy link

100terres commented Oct 16, 2020

We use ternary heavily:

export default function SomeTab() {
  // ...
}

if, switch have fatal flaws —— it is just statement rather than expression. Only expressions can be used as value.

@xiaoxiangmoe Personally as @vdh mention I would also prefer something like this 👇 based on your example.

export default function SomeTab() {
  const { data, error } = useSomeRequest();

  if (error) {
    return <Result status="error" />;
  }

  if (data === undefined) {
    return <Spin />;
  }

  if (data.length === 0) {
    return <Empty />;
  }

  return (
    <div>
      {data.map((log, i) => (
        <div>{log.message}</div>
      ))}
    </div>
  );
}

As for your point on const variable assignment, the examplanation here is good enought for me.

@xiaoxiangmoe
Copy link

xiaoxiangmoe commented Oct 19, 2020

Ternary is poor man's pattern matching.
Another example:

export default function SomeTab({ data }) {
  return (
    <div>
      {data.map((x, i) => (
        <div>
          {x.type === 'foo' ? (
            <Foo x={x.foo}></Foo>
          ) : x.type === 'bar' ? (
            <Bar>{x}</Bar>
          ) : x.type === 'baz' ? (
            <Baz baz={x}></Baz>
          ) : (
            <div>{x.message}</div>
          )}
        </div>
      ))}
    </div>
  );
}

@ljharb
Copy link
Collaborator Author

ljharb commented Oct 19, 2020

@xiaoxiangmoe as a more debuggable and arguably cleaner alternative:

function FooType({ x: { foo } }) {
  return <Foo x={foo} />;
}
function BarType({ x }) {
  return <Bar>{x}</Bar>;
}
function BazType({ x }) {
  return <Baz baz={x} />;
}
function DefaultType({ x: { message } }) {
  return <div>{message}</div>;
}
const Lookup = {
  foo: FooType,
  bar: BarType,
  baz: BazType,
  default: DefaultType,
};

export default function SomeTab({ data }) {
  return (
    <div>
      {data.map((x) => {
        const C = lookup[x.type] || lookup.default;
        return (
          <div>
            <C x={x} />
          </div>
        );
      }}
    </div>
  );
}

@xiaoxiangmoe
Copy link

Another example:

const productId =
  process.platform === 'darwin'
    ? 110
    : process.platform === 'linux'
    ? 115
    : process.platform === 'win32' && process.arch === 'ia32'
    ? 121
    : process.platform === 'win32' && process.arch === 'x64'
    ? 120
    : undefined;

@ljharb
Copy link
Collaborator Author

ljharb commented Oct 19, 2020

@xiaoxiangmoe

const productIDs = {
  darwin: 110,
  linux: 115,
};
const win32s = {
  ia32: 121,
  x64: 120,
};
const productId = process.platform === 'win32' ? win32s[process.arch] : productIDs[process.platform];

I've never seen a nested ternary that needs to be one.

@thernstig
Copy link

@ljharb a lot of upvotes for no-use-before-define set to "nofunc". Is it worth considering to implement this soon?

@ljharb
Copy link
Collaborator Author

ljharb commented Jan 28, 2022

@thernstig nope, because this project doesn’t make decisions based on voting. Functions always belong declared before they’re used.

@thernstig
Copy link

Apologizes @ljharb I have must have misunderstood the purpose of it being listed in the main thread, but thanks for clarifying.

@mistic100
Copy link

I use allowTemplateLiterals for quotes even when there is no interpolation, because I don't want to use double quotes at all but I also don't want to have to escape single quotes.

@seyfer
Copy link

seyfer commented May 28, 2023

  rules: {
    'max-len': ['error', {
      code: 120,
      tabWidth: 2,
    }],
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'import/extensions': 'off',
    'import/no-unresolved': 'off',
    'import/no-extraneous-dependencies': 'off',
    'import/prefer-default-export': 'off',
    'no-unused-vars': 'warn',
    'no-unused-expressions': 'off',
    'no-return-assign': 'off',
    'no-shadow': 'off',
    'no-useless-constructor': 'off',
    'no-plusplus': 'off',
    'no-empty-function': 'off',
    'no-restricted-syntax': 'off',
    'no-loop-func': 'off',
    'no-await-in-loop': 'off',
    'no-promise-executor-return': 'off',
    'max-classes-per-file': 'off',
    'consistent-return': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/ban-types': 'off',
  },

@ljharb
Copy link
Collaborator Author

ljharb commented May 28, 2023

@seyfer im confused; why would a linter ever be run in production? (tests shouldn’t have NODE_ENV set to production)

@seyfer
Copy link

seyfer commented Jun 29, 2023

@ljharb I have a custom git commit hook that does run before the production build. If there is a lint error, it will stop the build.
In this way, in our team, we are sure that no wrong code style will be pushed to the repo.

@ljharb
Copy link
Collaborator Author

ljharb commented Jun 29, 2023

That's something you check in CI before you allow merging; the production build is usually done only on merged code (after these checks are run).

Also, clientside git hooks can be easily bypassed, so unless you run them on the server they're useless for enforcement.

@3x071c
Copy link

3x071c commented Nov 16, 2023

Thought I'd chip in:

{
    "import/prefer-default-export": "off" /* Expandability of named exports over pushing for unwanted default exports & API changes */,
    "no-cond-assign": [
        "error",
        "except-parens",
    ] /* Allow clear & concise assignments in conditionals, when necessary */,
    "no-void": [
        "error",
        { allowAsStatement: true },
    ] /* Allows void-calling async functions from sync code, explicit void returns, shorthand expressions */,
    "require-await": "error" /* Prevent refactoring mistakes and async creep */,
}

@seyfer
Copy link

seyfer commented Apr 5, 2024

@seyfer im confused; why would a linter ever be run in production? (tests shouldn’t have NODE_ENV set to production)

this is for Staging. when the requirement is to have staging as close to production as possible, and there is not so much difference for a simple build, there is no need to use specific .env.staging env. or envs could be supplied differently, f.e. from ENV_* variables.

I think you should not run CI/CD on the dev/local build, but as close to prod as possible.

@ljharb
Copy link
Collaborator Author

ljharb commented Apr 5, 2024

Linting is static analysis; it isn't run on a build, it's run on source files, which are identical everywhere. Your argument holds for tests, but not for linting.

@seyfer
Copy link

seyfer commented Apr 6, 2024

@ljharb my argument is for CI/CD in general, which usually does include code style checks, as well as other checks, such as complexity, etc. CI/CD needs to be automated, it runs f.e. with Jenkins or GitHub Actions, doesn't matter. And then, since it runs as one of the steps in the same workflow as tests and builds do run, they happen to use the same environment, and most often it is staging env (or prod, if there is no dedicated env). You can of course add dedicated staging, ci_cd or else name env and a config file per env, but that would be a bit more complex setup with the code duplication. It is easier, and actually makes sense, to use the same rules set in all envs for your project. And then, for a couple of rules, you don't want your build to fail with an error. Those couple of env checks are easier to add to the existing config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests