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!: Require schemas and object-style rules #85

Merged
merged 9 commits into from Mar 24, 2022

Conversation

bmish
Copy link
Sponsor Member

@bmish bmish commented Dec 13, 2021

Summary

We propose two changes to rules:

  • Requiring rules with options to specify schemas (removing support for rules with options that are missing schemas)
  • Requiring rule implementations to use the object-style format (removing support for the deprecated function-style format)

Demonstrations

Related Issues

Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall very much in favor of these changes. Just a few notes.

The two proposals in this RFC should be implemented separately.

### Detailed design for requiring schemas

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these changes will need to be made in the flat config files as we aren’t making any further changes to the eslintrc format.

designs/2021-schema-object-rules/README.md Show resolved Hide resolved
Comment on lines 26 to 43
Today, rule schemas ([meta.schema](https://eslint.org/docs/developer-guide/working-with-rules#rule-basics)) are optional. This has the following effects:

- If a rule does not specify a schema, nothing is enforced.
- Rules with options may not have a schema specified, despite it being best practice to specify a schema for rule options. This increases the chances of developers passing invalid rule options and encountering unexpected behavior / bugs in rules.
- If a rule author wants to enforce that no options are passed to their rule, they have to manually specify `schema: []`, otherwise it's possible that developers could accidentally provide useless rule options to their rule without knowing it.

So we will change the default rule schema to `schema: []` to enforce that no rule options are passed to rules that do not specify a schema.

This has the desirable consequence of requiring rules with options to begin specifying their schema if they did not already.

Making ESLint's default behavior more strict like this goes along with many recent changes to tighten validation (i.e. more [RuleTester class validation in ESLint 7](https://eslint.org/docs/user-guide/migrating-to-7.0.0#rule-tester-strict), [increased rule configuration validation in ESLint 6](https://eslint.org/docs/user-guide/migrating-to-6.0.0#rule-config-validating), etc) to improve rule quality and usability and reduce the chance of mistakes.

And in addition to the obvious benefits of increased rule schema usage such as reduced chance of silent mistakes/bugs, being able to rely on having rule schemas available could unlock new features/improvements:

- Improved/auto-generated documentation regarding rule options
- TypeScript-style auto-complete/IntelliSense when configuring a rule

There is an existing, third-party lint rule [eslint-plugin/require-meta-schema](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-schema.md) to enforce that rules have schemas.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm 👍to change the default to [], but do we want to add a new way to allow rule authors explicitly skipping the validating - e.g. schema: false?
I'm asking as it could happen in some rare cases, e.g. writing an internal rule.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm to change the default to [], but do we want to add a new way to allow rule authors explicitly skipping the validating - e.g. schema: false?

A workaround can be schema: {} or schema: { type: "array" }

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see both sides, but my current preference is to not provide an explicit opt-out.

Why we shouldn't give users an explicit schema opt-out:

  • An easy, well-known option to opt-out is just welcoming laziness, and many plugins authors may default to always using it, despite the benefits and ease of writing a real schema, defeating many of the benefits described in this RFC
  • An explicit opt-out option is essentially encouraging/recommending opting-out as an acceptable approach
  • As @mdjermanovic suggested, users can use the the workaround of providing an incomplete schema likeschema: {} or schema: { type: "array" } if they really want to opt-out
  • In my opinion, schemas should be low-effort and valuable for all rules, even internal rules

Why we should give users an explicit schema opt-out like schema: false:

  • If users are going to abuse obscure workarounds like schema: {} or schema: { type: "array" } anyway, then it would be clearer for everyone if they could explicitly specify that they don't want a schema

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a paragraph about this workaround in the Drawbacks of requiring schemas section including touching on why I am against an explicit opt-out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both arguments for and arguments against schema: false are good.

Why we should give users an explicit schema opt-out like schema: false:

  • If users are going to abuse obscure workarounds like schema: {} or schema: { type: "array" } anyway, then it would be clearer for everyone if they could explicitly specify that they don't want a schema

I suggested the workarounds, but this is a very good argument and I'm now for an explicit opt-out like schema: false.

As described in the Motivation section of this RFC, one of the benefits of rule schemas (and thus forcing rules to define schemas) is automated analysis of rule options, like auto-generated docs, auto-complete, maybe even detecting breaking changes as mentioned in eslint/eslint#15428, etc., For that purpose, when some rule doesn't provide a schema for whatever reason, it seems better to see that right away from schema: false than to analyze a meaningless workaround schema.

Also, some schema validations we might want to add to RuleTester in the future could report these workaround schemas as potentially incorrect, since they're essentially a no-op.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One follow-up to this, I think we should disallow schema: {}. That is the simplest no-op schema and can be easily confused with schema: [] which actually does enforce that there are no options. Anyone using schema: {} is likely making a mistake, so we can throw an error if we detect that:

`schema: {}` is a no-op. For rules with options, please fill in a complete schema. For rules without options, please omit `schema` or use `schema: []`.

We could also disallow other obvious ones like schema: { type: "array" } but I'm not too worried about catching all possible no-op schemas right now.

Copy link
Sponsor

@ljharb ljharb Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, i had no idea that {} works this way.

would { additionalProperties: false } be the same as []?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema: { additionalProperties: false } is also a no-op.

Copy link
Sponsor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, maybe { maxProperties: 0 } or something, but clearly [] is simpler

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the Opt-out section to mention that we will disallow a few common no-op schemas.

Comment on lines 173 to 178
- Adding deprecation notices inside ESLint in a minor version release, showing a warning when executing linting for an offending rule:
> DEPRECATION WARNING: This rule has options but is missing `meta.schema` and will stop working in ESLint v9.
> The maintainer of this rule needs to release an updated version with a schema added.
>
> DEPRECATION WARNING: This rule is using the deprecated function-style format and will stop working in ESLint v9.
> The maintainer of this rule needs to release an updated version using object-style format.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary, since we will have deprecation warnings when running tests?

Showing warnings when executing linting could be annoying to end users, as there's nothing they can do to fix the issue.

Copy link
Sponsor Member Author

@bmish bmish Dec 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping that showing the deprecation warning to end users would result in them pinging the maintainer / filing an issue on github / or even opening a PR themselves with the fix. This would mainly be necessary for stagnant eslint plugins where the maintainer might not otherwise notice the deprecation warning since they aren't regularly running tests.

Perhaps I can propose a different timeline however:

  1. When this RFC is approved, add the deprecation warning when running tests, in the hopes of most affected plugins seeing this.
  2. In the final minor version of ESLint 8, add the deprecation warning for end users when running linting to try to catch any last holdouts.
  3. In ESLint 9, change the deprecation warnings into fatal assertions.

But if we still think the deprecation warning for end users is too obtrusive, I'm not attached to it and we can skip it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for not showing warnings to end users since we have other ways to notify maintainers: deprecation warnings when running tests, blog posts, and prerelease versions. If all of that fails to notify some plugins, the worst case is that they'll be notified by users when we release ESLint 9 final.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with @mdjermanovic. The prerelease versions should give users/plugins time to discover any remaining problems. Updated RFC to only add a deprecation warning when running tests.

to existing users?
-->

This RFC describes a set of breaking changes that can be released in the next major version of ESLint. Both ESLint plugin authors and ESLint users will be affected and will be unable to use offending rules until they are updated. This will mostly affect a small number of older ESLint plugins that haven't been updated nor maintained in years.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be also a breaking change for end users who have invalid configurations for non-offending rules (those that don't expect any options), as they'll have to remove useless options from config files.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a bullet point to denote this.

Copy link
Member

@nzakas nzakas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m happy with the overall direction at this point. 👍

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from resolving the discussion about schema opt-outs (#85 (comment)), everything else looks good to me, and the analysis of plugins is awesome!

Comment on lines 179 to 181
> DEPRECATION WARNING: This rule has options but is missing `meta.schema` and will stop working in ESLint v9.
> Please add `meta.schema`: <https://eslint.org/docs/developer-guide/working-with-rules#options-schemas>
> This lint rule can assist with the conversion: <https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/main/docs/rules/require-meta-schema.md>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we clarify here in the RFC document that this check is triggered by test cases that have options property? (as implemented in bmish/eslint@b7bb2c0)

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated deprecation warning text to clarify when it is triggered.

Copy link
Member

@btmills btmills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM pending addition of schema: false. Thank you for building the ecosystem analyzer; this is the sort of change I would be very nervous about making without data!

@nzakas
Copy link
Member

nzakas commented Mar 15, 2022

@mdjermanovic do you feel comfortable moving to final commenting?

@bmish
Copy link
Sponsor Member Author

bmish commented Mar 15, 2022

Addressed all comments.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks!

@mdjermanovic
Copy link
Member

@mdjermanovic do you feel comfortable moving to final commenting?

Yes, moving to final commenting now.

@mdjermanovic mdjermanovic added Final Commenting This RFC is in the final week of commenting and removed Initial Commenting This RFC is in the initial feedback stage labels Mar 16, 2022

We propose two changes to rules:

- Requiring rules with options to specify schemas (removing support for rules with options that are missing schemas)
Copy link
Sponsor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good change; a few of my plugins have rules that lack a schema because it’s a breaking change, but my planned workaround for those is to conditionally use an empty schema for eslint 9+

We propose two changes to rules:

- Requiring rules with options to specify schemas (removing support for rules with options that are missing schemas)
- Requiring rule implementations to use the object-style format (removing support for the deprecated function-style format)
Copy link
Sponsor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which versions of eslint, if any, only supported the function format? What capabilities, if any, are only available in the function format?

Copy link
Sponsor Member Author

@bmish bmish Mar 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just did some testing and found that ESLint v1 only supported function-style rules (object-style rules will throw an error rule is not a function). It looks like ESLint v2 began supporting object-style rules too.

I'm pretty confident that function-style rules do not offer any extra functionality over object-style rules.


- If a rule does not specify a schema, nothing is enforced.
- Rules with options may not have a schema specified, despite it being best practice to specify a schema for rule options. This increases the chances of developers passing invalid rule options and encountering unexpected behavior / bugs in rules.
- If a rule author wants to enforce that no options are passed to their rule, they have to manually specify `schema: []`, otherwise it's possible that developers could accidentally provide useless rule options to their rule without knowing it.
Copy link
Sponsor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe schema: {} works as well?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tested to confirm that schema: {} does not actually enforce anything. As mentioned in comments like #85 (comment), we should probably disallow no-op schemas like that. If someone wants to enforce that there are no options, they should omit the schema (after this RFC) or use schema: []. If someone wants to skip writing a schema, they will be able to use schema: false.

Copy link
Member

@aladdin-add aladdin-add left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM~

Comment on lines 102 to 106
We believe this explicit opt-out is preferred over users resorting to hacky workarounds such as providing no-op/fake/minimal/incomplete schemas like `schema: {}` or `schema: { type: "array" }`. No-op schemas like these can confuse both humans (who might mistake it with `schema: []` which is not a no-op) and automated analysis/tooling built around schemas. So we will throw an error when encountering a few common versions of these no-op schemas:

```pt
`schema: {}` is a no-op. For rules with options, please fill in a complete schema. For rules without options, please omit `schema` or use `schema: []`.
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a consensus on all changes proposed by this RFC, but this looks like an entirely new addition to the proposal so we should either revert to the approved version or dismiss all approvals and seek consensus again.

I'm not opposed to evaluating this, but we should clarify details like:

  • Where will this check be performed? (RuleTester or core, and if it's in the core then where exactly).
  • Which schemas will be reported?
  • Should we log a warning in ESLint v8?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it causes a problem, I'm happy to revert this new exception. I figured this RFC would be a good chance to include it since it's very related, and you suggested it originally in the other thread. Definitely want to get the full TSC's opinion on it. I can fill in more details soon.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, once an RFC is in final commenting, we shouldn’t be making any further additions, only adding clarifications.

I think it makes sense to bundle this change with the rest of the RFC but the additional details @mdjermanovic mentioned would be helpful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think it makes sense to include this change in this RFC, just waiting for details :)

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We can throw the exception in both core and rule tester. In core, we can throw it next to ajv.compile where the existing schema validation happens.
  2. Honestly, I'm fine just checking for schema: {} since that's the most-common mistake and most-easy to mix up with schema: []. It should be easy to add more to the list later if there's an interest or need.
  3. Yes I think we can log a warning in ESLint v8 when running tests for a rule with such a schema.

I used eslint-ecosystem-analyzer and found that about ~7 of the top 1,000 plugins have an occurrence of schema: {}.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the additional details, I completely agree with 2. and 3.

As for 1., reporting no-op schemas looks to me more like useful info for rule developers than a catastrophic error that should prevent the rule from working in production, so I would be more inclined to have this check only in RuleTester. We already have a somewhat similar check for no-op defaults in the RuleTester (strictDefaults: true) but not in the core. However, I don't have a strong opinion on this - if there is any benefit in throwing this error in the core, I could agree with that.

Either way, there's no need to duplicate checks. If we decide to implement this check in the core, then it doesn't have to be duplicated in the RuleTester, because the tests will fail anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agreed, there is nothing the plugin users can do to avoid the error. they could only disable the rule totally and report to the plugin author.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with only throwing the exception in rule tester for now. Although note that other schema validation happens in core already, so it could be argued that all validation should be done together for consistency instead of having different types of validation in different places.

I have updated this section of the RFC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although note that other schema validation happens in core already

Do we have validations of rule schemas in core? I think we've disabled Ajv validations and that we don't have any custom validations.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I just confirmed that we don't validate schemas in core. We do ajv.compile in core but ajv.validateSchema only in rule tester.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@btmills btmills merged commit 53d1b2d into eslint:main Mar 24, 2022
aladdin-add pushed a commit to aladdin-add/rfcs that referenced this pull request Jul 12, 2022
* feat!: Require schemas and object-style rules

* add more detail on deprecation warnings

* tweak deprecation notice

* address PR feedback

* schema: false

* clarify when deprecation warning shows

* mention when object-style rules were introduced

* disallow no-op schemas

* no-op schema rule tester only
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking feature Final Commenting This RFC is in the final week of commenting
Projects
None yet
6 participants