feat(linter): add jsx-a11y settings (#1668)

When we developed linter for #1141 , we needed to configure some
settings for `jsx-a11y`, which was not supported before, but I am trying
to support it now.
like this:
```
fn config() -> serde_json::Value {
    serde_json::json!([2,{
        "ignoreNonDOM": true
    }])
}

fn settings() -> serde_json::Value {
    serde_json::json!({
        "jsx-a11y": {
            "components": {
                "Button": "button",
            }
        }
    })
}

let pass = vec![
    ("<Button />", Some(config()), Some(settings())),
];
```
This commit is contained in:
msdlisper 2023-12-16 13:45:14 +08:00 committed by GitHub
parent cf91379d1b
commit 6a90cd4af4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 255 additions and 77 deletions

View file

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};
pub mod errors;
use oxc_diagnostics::{Error, FailedToOpenFileError, Report};
@ -7,7 +7,7 @@ use serde_json::Value;
use crate::{
rules::{RuleEnum, RULES},
AllowWarnDeny,
AllowWarnDeny, JsxA11y, LintSettings,
};
use self::errors::{
@ -17,6 +17,7 @@ use self::errors::{
pub struct ESLintConfig {
rules: std::vec::Vec<RuleEnum>,
settings: LintSettings,
}
impl ESLintConfig {
@ -66,6 +67,8 @@ impl ESLintConfig {
}
};
let settings = parse_settings_from_root(&file);
// `extends` provides the defaults
// `rules` provides the overrides
let rules = RULES.clone().into_iter().filter_map(|rule| {
@ -91,12 +94,16 @@ impl ESLintConfig {
}
});
Ok(Self { rules: rules.collect::<Vec<_>>() })
Ok(Self { rules: rules.collect::<Vec<_>>(), settings })
}
pub fn into_rules(mut self) -> Vec<RuleEnum> {
pub fn into_rules(mut self) -> Self {
self.rules.sort_unstable_by_key(RuleEnum::name);
self.rules
self
}
pub fn get_config(self) -> (std::vec::Vec<RuleEnum>, LintSettings) {
(self.rules, self.settings)
}
}
@ -151,6 +158,42 @@ fn parse_rules(
.collect::<Result<Vec<_>, Error>>()
}
fn parse_settings_from_root(root_json: &Value) -> LintSettings {
let Value::Object(root_object) = root_json else { return LintSettings::default() };
let Some(settings_value) = root_object.get("settings") else { return LintSettings::default() };
parse_settings(settings_value)
}
pub fn parse_settings(setting_value: &Value) -> LintSettings {
if let Value::Object(settings_object) = setting_value {
if let Some(Value::Object(jsx_a11y)) = settings_object.get("jsx-a11y") {
let mut jsx_a11y_setting =
JsxA11y { polymorphic_prop_name: None, components: HashMap::new() };
if let Some(Value::Object(components)) = jsx_a11y.get("components") {
let components_map: HashMap<String, String> = components
.iter()
.map(|(key, value)| (String::from(key), String::from(value.as_str().unwrap())))
.collect();
jsx_a11y_setting.set_components(components_map);
}
if let Some(Value::String(polymorphic_prop_name)) = jsx_a11y.get("polymorphicPropName")
{
jsx_a11y_setting
.set_polymorphic_prop_name(Some(String::from(polymorphic_prop_name)));
}
return LintSettings { jsx_a11y: jsx_a11y_setting };
}
}
LintSettings::default()
}
pub const EXTENDS_MAP: Map<&'static str, &'static str> = phf_map! {
"eslint:recommended" => "eslint",
"plugin:react/recommended" => "react",

View file

@ -8,7 +8,7 @@ use oxc_span::SourceType;
use crate::{
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
fixer::{Fix, Message},
AstNode,
AstNode, LintSettings,
};
pub struct LintContext<'a> {
@ -24,10 +24,12 @@ pub struct LintContext<'a> {
current_rule_name: &'static str,
file_path: Box<Path>,
settings: LintSettings,
}
impl<'a> LintContext<'a> {
pub fn new(file_path: Box<Path>, semantic: &Rc<Semantic<'a>>) -> Self {
pub fn new(file_path: Box<Path>, semantic: &Rc<Semantic<'a>>, settings: LintSettings) -> Self {
let disable_directives =
DisableDirectivesBuilder::new(semantic.source_text(), semantic.trivias()).build();
Self {
@ -37,6 +39,7 @@ impl<'a> LintContext<'a> {
fix: false,
current_rule_name: "",
file_path,
settings,
}
}
@ -54,6 +57,10 @@ impl<'a> LintContext<'a> {
&self.disable_directives
}
pub fn settings(&self) -> LintSettings {
self.settings.clone()
}
pub fn source_text(&self) -> &'a str {
self.semantic().source_text()
}

View file

@ -17,7 +17,7 @@ mod rules;
mod service;
mod utils;
use std::{self, fs, io::Write, rc::Rc, time::Duration};
use std::{self, collections::HashMap, fs, io::Write, rc::Rc, time::Duration};
use oxc_diagnostics::Report;
pub(crate) use oxc_semantic::AstNode;
@ -33,10 +33,38 @@ pub use crate::{
};
pub(crate) use rules::{RuleEnum, RULES};
#[derive(Debug, Clone)]
pub struct LintSettings {
jsx_a11y: JsxA11y,
}
impl Default for LintSettings {
fn default() -> Self {
Self { jsx_a11y: JsxA11y { polymorphic_prop_name: None, components: HashMap::new() } }
}
}
#[derive(Debug, Clone)]
pub struct JsxA11y {
polymorphic_prop_name: Option<String>,
components: HashMap<String, String>,
}
impl JsxA11y {
pub fn set_components(&mut self, components: HashMap<String, String>) {
self.components = components;
}
pub fn set_polymorphic_prop_name(&mut self, name: Option<String>) {
self.polymorphic_prop_name = name;
}
}
#[derive(Debug)]
pub struct Linter {
rules: Vec<RuleEnum>,
options: LintOptions,
settings: LintSettings,
}
impl Default for Linter {
@ -52,15 +80,21 @@ impl Linter {
.filter(|&rule| rule.category() == RuleCategory::Correctness)
.cloned()
.collect::<Vec<_>>();
Self { rules, options: LintOptions::default() }
Self {
rules,
options: LintOptions::default(),
settings: LintSettings {
jsx_a11y: JsxA11y { polymorphic_prop_name: None, components: HashMap::new() },
},
}
}
/// # Errors
///
/// Returns `Err` if there are any errors parsing the configuration file.
pub fn from_options(options: LintOptions) -> Result<Self, Report> {
let rules = options.derive_rules()?;
Ok(Self { rules, options })
let (rules, settings) = options.derive_rules_and_settings()?;
Ok(Self { rules, options, settings })
}
#[must_use]
@ -69,6 +103,12 @@ impl Linter {
self
}
#[must_use]
pub fn with_settings(mut self, settings: LintSettings) -> Self {
self.settings = settings;
self
}
pub fn rules(&self) -> &Vec<RuleEnum> {
&self.rules
}
@ -120,6 +160,9 @@ impl Linter {
ctx.into_message()
}
pub fn get_settings(&self) -> LintSettings {
self.settings.clone()
}
#[allow(unused)]
fn read_rules_configuration() -> Option<serde_json::Map<String, serde_json::Value>> {
fs::read_to_string(".eslintrc.json")

View file

@ -9,7 +9,7 @@ use crate::{
ESLintConfig,
},
rules::RULES,
RuleCategory, RuleEnum,
LintSettings, RuleCategory, RuleEnum,
};
use oxc_diagnostics::{Error, Report};
use rustc_hash::FxHashSet;
@ -145,12 +145,12 @@ const JSX_A11Y_PLUGIN_NAME: &str = "jsx_a11y";
impl LintOptions {
/// # Errors
/// Returns `Err` if there are any errors parsing the configuration file.
pub fn derive_rules(&self) -> Result<Vec<RuleEnum>, Report> {
pub fn derive_rules_and_settings(&self) -> Result<(Vec<RuleEnum>, LintSettings), Report> {
let mut rules: FxHashSet<RuleEnum> = FxHashSet::default();
if let Some(path) = &self.config_path {
let rules = ESLintConfig::new(path)?.into_rules();
return Ok(rules);
let (rules, settings) = ESLintConfig::new(path)?.into_rules().get_config();
return Ok((rules, settings));
}
let all_rules = self.get_filtered_rules();
@ -195,7 +195,7 @@ impl LintOptions {
let mut rules = rules.into_iter().collect::<Vec<_>>();
// for stable diagnostics output ordering
rules.sort_unstable_by_key(RuleEnum::name);
Ok(rules)
Ok((rules, LintSettings::default()))
}
// get final filtered rules by reading `self.jest_plugin` and `self.jsx_a11y_plugin`

View file

@ -1,6 +1,6 @@
use phf::phf_set;
use oxc_ast::{ast::JSXElementName, AstKind};
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
@ -8,7 +8,12 @@ use oxc_diagnostics::{
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, utils::has_jsx_prop, AstNode};
use crate::{
context::LintContext,
rule::Rule,
utils::{get_element_type, has_jsx_prop},
AstNode,
};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users")]
@ -86,12 +91,11 @@ impl Rule for NoAutofocus {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::JSXElement(jsx_el) = node.kind() {
if let Option::Some(autofocus) = has_jsx_prop(&jsx_el.opening_element, "autoFocus") {
let Some(element_type) = get_element_type(ctx, &jsx_el.opening_element) else {
return;
};
if self.ignore_non_dom {
let JSXElementName::Identifier(ident) = &jsx_el.opening_element.name else {
return;
};
let name = ident.name.as_str();
if HTML_TAG.contains(name) {
if HTML_TAG.contains(&element_type) {
if let oxc_ast::ast::JSXAttributeItem::Attribute(attr) = autofocus {
ctx.diagnostic(NoAutofocusDiagnostic(attr.span));
}
@ -262,38 +266,46 @@ const HTML_TAG: phf::Set<&'static str> = phf_set! {
#[test]
fn test() {
use crate::tester::Tester;
fn array() -> serde_json::Value {
fn config() -> serde_json::Value {
serde_json::json!([2,{
"ignoreNonDOM": true
}])
}
fn settings() -> serde_json::Value {
serde_json::json!({
"jsx-a11y": {
"components": {
"Button": "button",
}
}
})
}
let pass = vec![
("<div />;", None),
("<div autofocus />;", None),
("<input autofocus='true' />;", None),
("<Foo bar />", None),
("<Button />", None),
("<Foo autoFocus />", Some(array())),
("<div><div autofocus /></div>", Some(array())),
// TODO we need components_settings to test this
// ("<Button />", Some(serde_json::json!(ignoreNonDOMSchema))),
// ("<Button />", Some(serde_json::json!(ignoreNonDOMSchema)), setting),
("<div />;", None, None),
("<div autofocus />;", None, None),
("<input autofocus='true' />;", None, None),
("<Foo bar />", None, None),
("<Button />", None, None),
("<Foo autoFocus />", Some(config()), None),
("<div><div autofocus /></div>", Some(config()), None),
("<Button />", None, Some(settings())),
("<Button />", Some(config()), Some(settings())),
];
let fail = vec![
("<div autoFocus />", None),
("<div autoFocus={true} />", None),
("<div autoFocus={false} />", None),
("<div autoFocus={undefined} />", None),
("<div autoFocus='true' />", None),
("<div autoFocus='false' />", None),
("<input autoFocus />", None),
("<Foo autoFocus />", None),
("<Button autoFocus />", None),
// TODO we need components_settings to test this
// ("<Button autoFocus />", Some(array())),
("<div autoFocus />", None, None),
("<div autoFocus={true} />", None, None),
("<div autoFocus={false} />", None, None),
("<div autoFocus={undefined} />", None, None),
("<div autoFocus='true' />", None, None),
("<div autoFocus='false' />", None, None),
("<input autoFocus />", None, None),
("<Foo autoFocus />", None, None),
("<Button autoFocus />", None, None),
("<Button autoFocus />", Some(config()), Some(settings())),
];
Tester::new(NoAutofocus::NAME, pass, fail).test_and_snapshot();
Tester::new_with_settings(NoAutofocus::NAME, pass, fail).test_and_snapshot();
}

View file

@ -169,6 +169,7 @@ impl Runtime {
}
}
#[allow(clippy::too_many_arguments)]
fn process_source<'a>(
&self,
path: &Path,
@ -233,8 +234,11 @@ impl Runtime {
return semantic_ret.errors.into_iter().map(|err| Message::new(err, None)).collect();
};
let lint_ctx =
LintContext::new(path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic));
let lint_ctx = LintContext::new(
path.to_path_buf().into_boxed_path(),
&Rc::new(semantic_ret.semantic),
self.linter.get_settings(),
);
self.linter.run(lint_ctx)
}

View file

@ -1,6 +1,6 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 119
assertion_line: 130
expression: no_autofocus
---
⚠ eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
@ -66,4 +66,11 @@ expression: no_autofocus
╰────
help: Remove `autofocus` attribute
⚠ eslint-plugin-jsx-a11y(no-autofocus): The `autofocus` attribute is found here, which can cause usability issues for sighted and non-sighted users
╭─[no_autofocus.tsx:1:1]
1 │ <Button autoFocus />
· ─────────
╰────
help: Remove `autofocus` attribute

View file

@ -8,7 +8,10 @@ use oxc_diagnostics::miette::{GraphicalReportHandler, GraphicalTheme, NamedSourc
use oxc_diagnostics::DiagnosticService;
use serde_json::Value;
use crate::{rules::RULES, Fixer, LintOptions, LintService, Linter, RuleEnum};
use crate::{
config::parse_settings, rules::RULES, Fixer, LintOptions, LintService, LintSettings, Linter,
RuleEnum,
};
#[derive(Eq, PartialEq)]
enum TestResult {
@ -20,8 +23,8 @@ enum TestResult {
pub struct Tester {
rule_name: &'static str,
rule_path: PathBuf,
expect_pass: Vec<(String, Option<Value>)>,
expect_fail: Vec<(String, Option<Value>)>,
expect_pass: Vec<(String, Option<Value>, Option<Value>)>,
expect_fail: Vec<(String, Option<Value>, Option<Value>)>,
expect_fix: Vec<(String, String, Option<Value>)>,
snapshot: String,
current_working_directory: Box<Path>,
@ -35,10 +38,28 @@ impl Tester {
rule_name: &'static str,
expect_pass: Vec<(S, Option<Value>)>,
expect_fail: Vec<(S, Option<Value>)>,
) -> Self {
let expect_pass =
expect_pass.into_iter().map(|(s, r)| (s.into(), r, None)).collect::<Vec<_>>();
let expect_fail =
expect_fail.into_iter().map(|(s, r)| (s.into(), r, None)).collect::<Vec<_>>();
Self::new_with_settings(rule_name, expect_pass, expect_fail)
}
pub fn new_with_settings<S: Into<String>>(
rule_name: &'static str,
expect_pass: Vec<(S, Option<Value>, Option<Value>)>,
expect_fail: Vec<(S, Option<Value>, Option<Value>)>,
) -> Self {
let rule_path = PathBuf::from(rule_name.replace('-', "_")).with_extension("tsx");
let expect_pass = expect_pass.into_iter().map(|(s, r)| (s.into(), r)).collect::<Vec<_>>();
let expect_fail = expect_fail.into_iter().map(|(s, r)| (s.into(), r)).collect::<Vec<_>>();
let expect_pass = expect_pass
.into_iter()
.map(|(s, r, settings)| (s.into(), r, settings))
.collect::<Vec<_>>();
let expect_fail = expect_fail
.into_iter()
.map(|(s, r, settings)| (s.into(), r, settings))
.collect::<Vec<_>>();
let current_working_directory =
env::current_dir().unwrap().join("fixtures/import").into_boxed_path();
Self {
@ -60,9 +81,11 @@ impl Tester {
expect_pass: Vec<S>,
expect_fail: Vec<S>,
) -> Self {
let expect_pass = expect_pass.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
let expect_fail = expect_fail.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
Self::new(rule_name, expect_pass, expect_fail)
let expect_pass =
expect_pass.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
let expect_fail =
expect_fail.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
Self::new_with_settings(rule_name, expect_pass, expect_fail)
}
pub fn update_expect_pass_fail<S: Into<String>>(
@ -70,8 +93,10 @@ impl Tester {
expect_pass: Vec<S>,
expect_fail: Vec<S>,
) -> Self {
self.expect_pass = expect_pass.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
self.expect_fail = expect_fail.into_iter().map(|s| (s.into(), None)).collect::<Vec<_>>();
self.expect_pass =
expect_pass.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
self.expect_fail =
expect_fail.into_iter().map(|s| (s.into(), None, None)).collect::<Vec<_>>();
self
}
@ -121,16 +146,16 @@ impl Tester {
}
fn test_pass(&mut self) {
for (test, config) in self.expect_pass.clone() {
let result = self.run(&test, config, false);
for (test, config, settings) in self.expect_pass.clone() {
let result = self.run(&test, config, false, &settings);
let passed = result == TestResult::Passed;
assert!(passed, "expect test to pass: {test} {}", self.snapshot);
}
}
fn test_fail(&mut self) {
for (test, config) in self.expect_fail.clone() {
let result = self.run(&test, config, false);
for (test, config, settings) in self.expect_fail.clone() {
let result = self.run(&test, config, false, &settings);
let failed = result == TestResult::Failed;
assert!(failed, "expect test to fail: {test}");
}
@ -138,7 +163,7 @@ impl Tester {
fn test_fix(&mut self) {
for (test, expected, config) in self.expect_fix.clone() {
let result = self.run(&test, config, true);
let result = self.run(&test, config, true, &None);
if let TestResult::Fixed(fixed_str) = result {
assert_eq!(expected, fixed_str);
} else {
@ -147,15 +172,26 @@ impl Tester {
}
}
fn run(&mut self, source_text: &str, config: Option<Value>, is_fix: bool) -> TestResult {
fn run(
&mut self,
source_text: &str,
config: Option<Value>,
is_fix: bool,
settings: &Option<Value>,
) -> TestResult {
let allocator = Allocator::default();
let rule = self.find_rule().read_json(config);
let lint_settings: LintSettings =
settings.as_ref().map_or_else(LintSettings::default, parse_settings);
let options = LintOptions::default()
.with_fix(is_fix)
.with_import_plugin(self.import_plugin)
.with_jest_plugin(self.jest_plugin)
.with_jsx_a11y_plugin(self.jsx_a11y_plugin);
let linter = Linter::from_options(options).unwrap().with_rules(vec![rule]);
let linter = Linter::from_options(options)
.unwrap()
.with_rules(vec![rule])
.with_settings(lint_settings);
let path_to_lint = if self.import_plugin {
self.current_working_directory.join(&self.rule_path)
} else {

View file

@ -8,7 +8,7 @@ use oxc_ast::{
};
use oxc_semantic::{AstNode, SymbolFlags};
use crate::LintContext;
use crate::{JsxA11y, LintContext, LintSettings};
pub fn is_create_element_call(call_expr: &CallExpression) -> bool {
if let Some(member_expr) = call_expr.callee.get_member_expr() {
@ -163,3 +163,18 @@ pub fn get_parent_es6_component<'a, 'b>(ctx: &'b LintContext<'a>) -> Option<&'b
None
})
}
pub fn get_element_type(context: &LintContext, element: &JSXOpeningElement) -> Option<String> {
let JSXElementName::Identifier(ident) = &element.name else {
return None;
};
let mut element_type = String::from(ident.name.as_str());
let LintSettings { jsx_a11y } = context.settings();
let JsxA11y { polymorphic_prop_name: _, components } = jsx_a11y;
if let Some(val) = components.get(&element_type) {
element_type = String::from(val);
}
Some(element_type)
}

View file

@ -23,7 +23,7 @@ fn run_individual_test(
) -> std::result::Result<Vec<Report>, Vec<Report>> {
use std::rc::Rc;
use oxc_linter::LintContext;
use oxc_linter::{LintContext, LintSettings};
let file_path = &test.relative_path.last().expect("there to be atleast 1 path part");
let source_text = &test.code;
@ -48,8 +48,11 @@ fn run_individual_test(
let semantic = Rc::new(semantic);
let mut lint_ctx =
LintContext::new(PathBuf::from(file_path).into_boxed_path(), &Rc::clone(&semantic));
let mut lint_ctx = LintContext::new(
PathBuf::from(file_path).into_boxed_path(),
&Rc::clone(&semantic),
LintSettings::default(),
);
let result = plugin.lint_file_with_rule(
&mut lint_ctx,

View file

@ -13,7 +13,7 @@ use oxc::{
span::SourceType,
transformer::{TransformOptions, TransformTarget, Transformer},
};
use oxc_linter::{LintContext, Linter};
use oxc_linter::{LintContext, LintSettings, Linter};
use oxc_prettier::{Prettier, PrettierOptions};
use oxc_query::{schema, Adapter, SCHEMA_TEXT};
use oxc_type_synthesis::{synthesize_program, Diagnostic as TypeCheckDiagnostic};
@ -205,7 +205,8 @@ impl Oxc {
self.save_diagnostics(semantic_ret.errors);
let semantic = Rc::new(semantic_ret.semantic);
let lint_ctx = LintContext::new(path.into_boxed_path(), &semantic);
let lint_ctx =
LintContext::new(path.into_boxed_path(), &semantic, LintSettings::default());
let linter_ret = Linter::new().run(lint_ctx);
let diagnostics = linter_ret.into_iter().map(|e| e.error).collect();
self.save_diagnostics(diagnostics);

View file

@ -13,7 +13,7 @@ use crate::walk::Walk;
use miette::NamedSource;
use oxc_allocator::Allocator;
use oxc_diagnostics::{miette, Error, Severity};
use oxc_linter::{LintContext, Linter};
use oxc_linter::{LintContext, LintSettings, Linter};
use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
@ -270,8 +270,11 @@ impl IsolatedLintHandler {
return Some(Self::wrap_diagnostics(path, &source_text, reports));
};
let mut lint_ctx =
LintContext::new(path.to_path_buf().into_boxed_path(), &Rc::new(semantic_ret.semantic));
let mut lint_ctx = LintContext::new(
path.to_path_buf().into_boxed_path(),
&Rc::new(semantic_ret.semantic),
LintSettings::default(),
);
{
if let Ok(guard) = plugin.read() {
if let Some(plugin) = &*guard {

View file

@ -10,7 +10,7 @@ use std::{path::PathBuf, rc::Rc};
use oxc_allocator::Allocator;
use oxc_benchmark::{criterion_group, criterion_main, BenchmarkId, Criterion};
use oxc_linter::{AllowWarnDeny, LintContext, LintOptions, Linter};
use oxc_linter::{AllowWarnDeny, LintContext, LintOptions, LintSettings, Linter};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::SourceType;
@ -38,7 +38,11 @@ fn bench_linter(criterion: &mut Criterion) {
let linter = Linter::from_options(lint_options).unwrap();
let semantic = Rc::new(semantic_ret.semantic);
b.iter(|| {
linter.run(LintContext::new(PathBuf::from("").into_boxed_path(), &semantic))
linter.run(LintContext::new(
PathBuf::from("").into_boxed_path(),
&semantic,
LintSettings::default(),
))
});
},
);