-
-
Notifications
You must be signed in to change notification settings - Fork 508
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(linter): domains and deps #4713
Conversation
CodSpeed Performance ReportMerging #4713 will not alter performanceComparing Summary
|
if let Some(only) = self.only { | ||
for selector in only { | ||
if RuleFilter::from(selector).match_group::<G>() { | ||
G::record_rules(self) | ||
} | ||
} | ||
} | ||
|
||
if let Some(skip) = self.skip { | ||
for selector in skip { | ||
if RuleFilter::from(selector).match_group::<G>() { | ||
G::record_rules(self) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was repeated logic, which I moved inside push_rule
Some suggestions/discussions: Because we are likely to have a domain for each dependency, should we enable the associated domain when we met a given dependency? Regarding domain value: instead of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice! I love this! I especially like this gives us the ability to automatically pick up on the enabling of rules, just by adding a dependency 👍
One thing I wonder about: It feels like dependencies
and domains
have quite a bit of overlap in intention, whereas the way they work they are actually orthogonal to one another. I haven't yet made up my mind whether this is useful or confusing.
For instance, if I have the react
dependency, but biome.json
contains { "domains": { "react": false } }
, will React be enabled or disabled? The dependencies
suggests it should be enabled, but the domain
was disabled. So which should it be?
I think I would've expected a slightly different variation maybe:
- Rules belong to one or more domains
- Domains can be auto-enabled through dependencies
- In the future, maybe we find other ways to auto-enable a domain, such as the
engines
field, or the presence oftsconfig.json
,deno.json
. - Maybe domains can also be used to define presets for:
- Globals (
process
,__dirname
,jest
, etc..) - Assumed imports that are always available (like the example of
vscode
for VS Code plugins)
- Globals (
Of course, that does require a separate solution to decide whether a rule within a domain is recommended or not. But I think we can do this using a small config change:
{
"linter": {
"domains": {
"test": "off", // or `false`,
"react": "all", // or `true`, enables all rules in the React domain
"node": "recommended" // enabled recommended rules in the Node.js domain (this is the default if the rule's prerequisites are met)
}
}
}
None of this is blocking, just thinking out loud on how we could make it even better!
{ | ||
"linter": { | ||
"domains": { | ||
"test": false, // rules around testing are disabled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be really neat if our biome init
script could make a best effort to detect what kind of folder structure and/or file name pattern is in use for tests. And based on that it could automatically populate the overrides
. For example:
{
"overrides": [{
"include": ["**/*.test.js"],
"linter": {
"domains": {
"test": true
}
}
}]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can go even further, and make this part of the RuleDomain
feature. A RuleDomain
can specify a Matcher
, and enable the rules if it path matches a certain glob. This logic is applied the same way as dependencies are applied, which means that if the user tinker with configuration, it's not applied anymore
And how do we define a rule that belongs to a domain and it's recommended? Another metadata field? Open to suggestions.
Even though this is true, I think they shouldn't be correlated, at all. It would be a logic complex.
That's a valid question. We can decide on the relative issue or here. IMHO our configuration always wins, regardless of external factors (manifest, other config files e.g. deno or TypeScript). This will be documented, so people know the order of priority. |
We could reuse the We could even remove |
Is there a strong rationale to change the semantics of settings that we already have? What advantages brings to the users? And to us? What are we simplifying? You convinced me when we talked about the relationship between a rule being recommended and the severity, and we found a good rational. In this case I can't seem to find a rational to provide to our users and contributors.
I fear this logic isn't too evident:
|
Hehe, I hadn’t seen @Conaclos ‘s original response while typing mine, but it sounds like we’re quite aligned :)
Agreed, this makes most sense to me as well. After all, the old semantic doesn’t make sense anymore if a rule also has a domain. Something that’s unconditionally recommended should by definition not be limited to a specific domain.
This is just a stretch too far for me. A default domain still feels meaningless to me as it goes against what domains are. It’s more the absence of a domain, really.
Why would it be complex? The way I would imagine “domain dependencies” to work would have the same semantics as what you’re proposing, a nice opt-in that gives great defaults if someone has a But in terms of config resolution, I think it would be less complex than what you’re proposing, because there can no longer be a conflict between the domain rules and the dependencies rules, as they’ve become a single thing.
Even documented, people will regularly ask about this because they have a habit of only reading documentation until after things bite them :) I would rather avoid this potential conflict between business rules alltogether. |
One more thing that is not very intuitive about having And what about a rule that’s part of the |
Not sure where the recommendation is coming from, because it isn't part of the current PR, let's take a step back.
With the current setup proposed in this PR, you can have a react project, with the default biome configuration (no domains), and you'll get diagnostics from the react rules. This should address the initial concern of yours regarding not recommending the react rules anymore. However, it's still not clear what you're proposing with a relationship between domains and dependencies. But as long as it addresses your initial concern, it's fine by me. |
Yeah, so what I’m suggesting is that rules can be associated to one or more domains, but they are not directly associated with dependencies anymore. For declare_lint_rule! {
/// Documentation
pub(crate) UseExhaustiveDependencies {
version: "next",
name: "useExhaustiveDependencies",
language: "js",
recommended: true,
domains: &[RuleDomain::React],
}
} Note the combination of So how do we activate the domain? Either through explicit configuration: {
"linter": {
"domains": {
"react": "recommended" // enables only the recommended rules within the domain, whereas "all"/true would enable them all
}
}
} But explicit configuration wouldn’t be necessary if you have the |
I'm not a big fan of this part, because it requires more maintenance for us in case the business logic of a domain changes, and this can't be documented inside the page of the rule in an automated way. Hopefully these new domains won't change that often. |
ok, I might have an 💡 |
I am on the same line as @arendjr.
We expect the number of rules belonging to a domain to grow. Maybe you are seeing the thing on a different perspective: assimilating domains to presets? |
No, I am more focused on creating a narrative that justifies the change in direction, that's all. We need to present these changes to our users and contributors, and we need to be good at explaining them, especially why. If we can't come up with a good explanation, I think the changes aren't worth exploring. |
What is making domains different from presets? |
I’m not sure what the preset proposal looked like, but the first thing that comes to mind is: the name. The word “domain” implies a subset of the whole. Compare the word domain to cities within a country. If someone doesn’t live in a city, they don’t live in a “default city”, they live in no city. With domain it’s the same, if something is not part of a domain, they’re not part of a “default domain” either. Presets are not like that, because the word carries different expectations. A default preset wouldn’t sound weird to me, whereas a default domain does. Then again, a preset does tend to imply that you pick one and don’t mix them, so I think the word domain works better here. |
a74dd93
to
a621d13
Compare
Co-authored-by: Arend van Beelen jr. <[email protected]>
ea8bb33
to
08b0e5d
Compare
@@ -484,6 +566,7 @@ macro_rules! declare_syntax_rule { | |||
version: $version, | |||
name: $name, | |||
language: $language, | |||
severity: biome_diagnostics::Severity::Error, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Syntax rules are errors by definition
c74314d
to
61a8947
Compare
61a8947
to
db55449
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice piece ot work :)
I left some suggestions.
By the way, what makes you change your mind about domain dependencies and recommended
for domains?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
&("vitest", ">=1.0.0"), | ||
], | ||
RuleDomain::Solid => &[&("solid", ">=1.0.0")], | ||
RuleDomain::Next => &[&("react", ">=16.0.0"), &("next", ">=14.0.0")], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So merely having React will also enable the Next domain. Is that intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the other way around, having the next domain will enable Next.js rules and react rules, by scanning the dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case I don’t think I understand how this works? If that’s the intention, shouldn’t we add next
to the dependencies for the React domain? Instead of adding react
to the dependencies of the Next domain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thinking, I don't think it will work. I was trying to be smart having one domain depending on another, but I believe it's going to get things more complex.
However, even adding the next dependency to the react domain won't work either. I'll change that part of the logic later
Co-authored-by: Victorien Elvinger <[email protected]> Co-authored-by: Arend van Beelen jr. <[email protected]>
You made a good point about the number of rules growing and finding a way to better group them. In the end, we didn't change the semantics of the recommendations for the users; it only has a different business logic for us. |
Summary
As part of #4662, this PR introduced two new features in our linter
domains
Closes #4701
Closes #4712
Domains are concepts shared by different rules. As opposed to groups:
A
RuleDomain
can specify (for now) manifest dependencies and analyzer globals, via two functions:RuleDomain::manifest_dependencies
RuleDomain::globals
These (and future functions) must be
const
functions, so they can be used when generating the automated documentation, and provide the relevant information for a domain/rule.Dependencies are a list of tuples. Each tuple contains the dependency's name and the range of versions available from. For example, it doesn't make sense to enable the react rules if the project uses a react version without hooks.
Important
Based on the discussion we just had in this PR, here's the business logic of activation of the rule with domains:
recommended: true
. It's only enabled when the relative domain is"recommended" or
"all"`.recommneded: true
."all"
.Unfortunately, the
package.json
detection is limited to Biome capabilities, so the support of monorepos is limited.Note
This new feature should address @arendjr concerns regarding the removal of
useExhasutiveDependencies
from the recommended rulesTechnical changes
biome_service
, but when we moved everything tobiome_configuration
, we forgot to move them too. I moved them in this PR.dependencies
anddomains
has been added to theLinterVisitor
type, which is in charge to select the enabled rules by looking that workspace settings (configuration) and the metadata registry.severity
; I did change the codegen to create a function that returns the severity of the rule. I realised this isn't needed because we can assign the correct severity of the rule when we callR::diagnostic()
inside the analyzer. Way less code :)I created a new type called
ProcessLint
to reduce the duplicated code in thelint
functions called by the different languages.depedencies
anddomains
can only be enabled isonly
isn't present, or it's empty. This is a business requirement because--only
wins over everything.Test Plan
I added various tests to ensure the new features work as expected.