Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
refactor(rome_service): better messaging for rule options (#4661)
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico authored Jul 6, 2023
1 parent 8f46efb commit 4071154
Show file tree
Hide file tree
Showing 12 changed files with 1,466 additions and 558 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ declare_rule! {
/// Since ECMAScript 2021, the escape sequences \8 and \9 have been defined as non-octal decimal escape sequences.
/// However, most JavaScript engines consider them to be "useless" escapes. For example:
///
/// ```js
/// ```js,ignore
/// "\8" === "8"; // true
/// "\9" === "9"; // true
/// ```
Expand All @@ -34,7 +34,11 @@ declare_rule! {
/// ```
///
/// ```js,expect_diagnostic
/// const x = "Don't use \8 and \9 escapes.";
/// const x = "Don't use \8 escape.";
/// ```
///
/// ```js,expect_diagnostic
/// const x = "Don't use \9 escape.";
/// ```
///
/// ```js,expect_diagnostic
Expand Down
118 changes: 82 additions & 36 deletions crates/rome_js_analyze/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ use crate::semantic_analyzers::nursery::use_naming_convention::{
use bpaf::Bpaf;
use rome_analyze::options::RuleOptions;
use rome_analyze::RuleKey;
use rome_deserialize::json::{has_only_known_keys, VisitJsonNode};
use rome_deserialize::json::VisitJsonNode;
use rome_deserialize::{DeserializationDiagnostic, VisitNode};
use rome_json_syntax::{JsonLanguage, JsonSyntaxNode};
use rome_rowan::SyntaxNode;
use rome_json_syntax::{AnyJsonValue, JsonLanguage, JsonMemberName, JsonObjectValue};
use rome_rowan::AstNode;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -43,13 +43,6 @@ impl FromStr for PossibleOptions {
}

impl PossibleOptions {
const KNOWN_KEYS: &'static [&'static str] = &[
"enumMemberCase",
"hooks",
"maxAllowedComplexity",
"strictCase",
];

pub fn extract_option(&self, rule_key: &RuleKey) -> RuleOptions {
match rule_key.rule_name() {
"noExcessiveComplexity" => {
Expand Down Expand Up @@ -79,44 +72,97 @@ impl PossibleOptions {
}
}

impl VisitJsonNode for PossibleOptions {}
impl VisitNode<JsonLanguage> for PossibleOptions {
fn visit_member_name(
impl PossibleOptions {
pub fn map_to_rule_options(
&mut self,
node: &JsonSyntaxNode,
value: &AnyJsonValue,
name: &str,
rule_name: &str,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<()> {
has_only_known_keys(node, PossibleOptions::KNOWN_KEYS, diagnostics)
let value = JsonObjectValue::cast_ref(value.syntax()).or_else(|| {
diagnostics.push(DeserializationDiagnostic::new_incorrect_type_for_value(
name,
"object",
value.range(),
));
None
})?;
for element in value.json_member_list() {
let element = element.ok()?;
let key = element.name().ok()?;
let value = element.value().ok()?;
let name = key.inner_string_text().ok()?;
self.validate_key(&key, rule_name, diagnostics)?;
match name.text() {
"hooks" => {
let mut options = HooksOptions::default();
self.map_to_array(&value, &name, &mut options, diagnostics)?;
*self = PossibleOptions::Hooks(options);
}
"maxAllowedComplexity" => {
let mut options = ComplexityOptions::default();
options.visit_map(key.syntax(), value.syntax(), diagnostics)?;
*self = PossibleOptions::Complexity(options);
}
"strictCase" | "enumMemberCase" => {
let mut options = match self {
PossibleOptions::NamingConvention(options) => *options,
_ => NamingConventionOptions::default(),
};
options.visit_map(key.syntax(), value.syntax(), diagnostics)?;
*self = PossibleOptions::NamingConvention(options);
}

_ => (),
}
}

Some(())
}

fn visit_map(
pub fn validate_key(
&mut self,
key: &SyntaxNode<JsonLanguage>,
value: &SyntaxNode<JsonLanguage>,
node: &JsonMemberName,
rule_name: &str,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<()> {
let (name, val) = self.get_key_and_value(key, value, diagnostics)?;
match name.text() {
"hooks" => {
let mut options = HooksOptions::default();
self.map_to_array(&val, &name, &mut options, diagnostics)?;
*self = PossibleOptions::Hooks(options);
let key_name = node.inner_string_text().ok()?;
let key_name = key_name.text();
match rule_name {
"useExhaustiveDependencies" | "useHookAtTopLevel" => {
if key_name != "hooks" {
diagnostics.push(DeserializationDiagnostic::new_unknown_key(
key_name,
node.range(),
&["hooks"],
));
}
}
"maxAllowedComplexity" => {
let mut options = ComplexityOptions::default();
options.visit_map(key, value, diagnostics)?;
*self = PossibleOptions::Complexity(options);
"useNamingConvention" => {
if !matches!(key_name, "strictCase" | "enumMemberCase") {
diagnostics.push(DeserializationDiagnostic::new_unknown_key(
key_name,
node.range(),
&["strictCase", "enumMemberCase"],
));
}
}
"strictCase" | "enumMemberCase" => {
let mut options = match self {
PossibleOptions::NamingConvention(options) => *options,
_ => NamingConventionOptions::default(),
};
options.visit_map(key, value, diagnostics)?;
*self = PossibleOptions::NamingConvention(options);
"noExcessiveComplexity" => {
if !matches!(key_name, "maxAllowedComplexity") {
diagnostics.push(DeserializationDiagnostic::new_unknown_key(
key_name,
node.range(),
&["maxAllowedComplexity"],
));
}
}
_ => (),
_ => {}
}

Some(())
}
}

impl VisitJsonNode for PossibleOptions {}
impl VisitNode<JsonLanguage> for PossibleOptions {}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ malformedOptions.options:9:7 deserialize ━━━━━━━━━━━━━
i Accepted keys
- enumMemberCase
- hooks
- maxAllowedComplexity
- strictCase
```
Expand Down
99 changes: 58 additions & 41 deletions crates/rome_service/src/configuration/parse/json/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,64 @@ impl VisitNode<JsonLanguage> for LinterConfiguration {
}
}

impl RuleConfiguration {
pub(crate) fn map_rule_configuration(
&mut self,
value: &AnyJsonValue,
key_name: &str,
rule_name: &str,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<()> {
let value = JsonObjectValue::cast_ref(value.syntax()).or_else(|| {
diagnostics.push(DeserializationDiagnostic::new_incorrect_type_for_value(
key_name,
"object",
value.range(),
));
None
})?;
for element in value.json_member_list() {
let element = element.ok()?;
let key = element.name().ok()?;
let value = element.value().ok()?;
let (name, value) =
self.get_key_and_value(key.syntax(), value.syntax(), diagnostics)?;
let name_text = name.text();
match name_text {
"level" => {
if let RuleConfiguration::WithOptions(options) = self {
let mut level = RulePlainConfiguration::default();
level.visit_member_value(value.syntax(), diagnostics)?;
options.level = level;
} else {
let mut level = RulePlainConfiguration::default();
level.visit_member_value(value.syntax(), diagnostics)?;
*self = RuleConfiguration::WithOptions(RuleWithOptions {
level,
..RuleWithOptions::default()
})
}
}
"options" => {
let mut possible_options = PossibleOptions::default();

possible_options.map_to_rule_options(&value, name_text, rule_name, diagnostics);
if let RuleConfiguration::WithOptions(options) = self {
options.options = Some(possible_options)
} else {
*self = RuleConfiguration::WithOptions(RuleWithOptions {
options: Some(possible_options),
..RuleWithOptions::default()
})
}
}
_ => {}
}
}
Some(())
}
}

impl VisitJsonNode for RuleConfiguration {}

impl VisitNode<JsonLanguage> for RuleConfiguration {
Expand Down Expand Up @@ -82,47 +140,6 @@ impl VisitNode<JsonLanguage> for RuleConfiguration {
}
Some(())
}

fn visit_map(
&mut self,
key: &SyntaxNode<JsonLanguage>,
value: &SyntaxNode<JsonLanguage>,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<()> {
let (name, value) = self.get_key_and_value(key, value, diagnostics)?;
let name_text = name.text();
match name_text {
"level" => {
if let RuleConfiguration::WithOptions(options) = self {
let mut level = RulePlainConfiguration::default();
level.visit_member_value(value.syntax(), diagnostics)?;
options.level = level;
} else {
let mut level = RulePlainConfiguration::default();
level.visit_member_value(value.syntax(), diagnostics)?;
*self = RuleConfiguration::WithOptions(RuleWithOptions {
level,
..RuleWithOptions::default()
})
}
}
"options" => {
let mut possible_options = PossibleOptions::default();
self.map_to_object(&value, name_text, &mut possible_options, diagnostics);
if let RuleConfiguration::WithOptions(options) = self {
options.options = Some(possible_options)
} else {
*self = RuleConfiguration::WithOptions(RuleWithOptions {
options: Some(possible_options),
..RuleWithOptions::default()
})
}
}
_ => {}
}

Some(())
}
}

impl VisitJsonNode for RulePlainConfiguration {}
Expand Down
Loading

0 comments on commit 4071154

Please sign in to comment.