Skip to content

Commit

Permalink
feat(linter/jsdoc): Implement require-property-(type|name|description…
Browse files Browse the repository at this point in the history
  • Loading branch information
leaysgur committed Apr 17, 2024
1 parent 722d4c2 commit 5d89e75
Show file tree
Hide file tree
Showing 7 changed files with 637 additions and 0 deletions.
6 changes: 6 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,9 @@ mod jsdoc {
pub mod check_property_names;
pub mod empty_tags;
pub mod require_property;
pub mod require_property_description;
pub mod require_property_name;
pub mod require_property_type;
}

mod tree_shaking {
Expand Down Expand Up @@ -690,5 +693,8 @@ oxc_macros::declare_all_lint_rules! {
jsdoc::check_property_names,
jsdoc::empty_tags,
jsdoc::require_property,
jsdoc::require_property_type,
jsdoc::require_property_name,
jsdoc::require_property_description,
tree_shaking::no_side_effects_in_initialization,
}
192 changes: 192 additions & 0 deletions crates/oxc_linter/src/rules/jsdoc/require_property_description.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-jsdoc(require-property-description): Missing description in @property tag.")]
#[diagnostic(severity(warning), help("Add a description to this @property tag."))]
struct RequirePropertyDescriptionDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
pub struct RequirePropertyDescription;

declare_oxc_lint!(
/// ### What it does
/// Requires that all `@property` tags have descriptions.
///
/// ### Why is this bad?
/// The description of a property should be documented.
///
/// ### Example
/// ```javascript
/// // Passing
/// /**
/// * @typedef {SomeType} SomeTypedef
/// * @property {number} foo Foo.
/// */
///
/// // Failing
/// /**
/// * @typedef {SomeType} SomeTypedef
/// * @property {number} foo
/// */
/// ```
RequirePropertyDescription,
correctness
);

impl Rule for RequirePropertyDescription {
fn run_once(&self, ctx: &LintContext) {
let settings = &ctx.settings().jsdoc;
let resolved_property_tag_name = settings.resolve_tag_name("property");

for jsdoc in ctx.semantic().jsdoc().iter_all() {
for tag in jsdoc.tags() {
let tag_kind = tag.kind;

if tag_kind.parsed() != resolved_property_tag_name {
continue;
}
let (_, _, comment_part) = tag.type_name_comment();
if !comment_part.parsed().is_empty() {
continue;
};

ctx.diagnostic(RequirePropertyDescriptionDiagnostic(tag_kind.span));
}
}
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
(
"
/**
* @typedef {SomeType} SomeTypedef
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property foo Foo.
*/
",
None,
None,
),
(
"
/**
* @namespace {SomeType} SomeName
* @property foo Foo.
*/
",
None,
None,
),
(
"
/**
* @class
* @property foo Foo.
*/
",
None,
None,
),
(
"
/**
* Typedef with multi-line property type.
*
* @typedef {object} MyType
* @property {function(
* number
* )} numberEater Method which takes a number.
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @prop foo ok
*/
",
None,
Some(serde_json::json!({
"jsdoc": {
"tagNamePreference": {
"property": "prop",
},
},
})),
),
];

let fail = vec![
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property foo
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property {string}
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property {string} foo
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @prop foo
*/
",
None,
Some(serde_json::json!({
"jsdoc": {
"tagNamePreference": {
"property": "prop",
},
},
})),
),
];

Tester::new(RequirePropertyDescription::NAME, pass, fail).test_and_snapshot();
}
153 changes: 153 additions & 0 deletions crates/oxc_linter/src/rules/jsdoc/require_property_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{context::LintContext, rule::Rule};

#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-jsdoc(require-property-name): Missing name in @property tag.")]
#[diagnostic(severity(warning), help("Add a type name to this @property tag."))]
struct RequirePropertyNameDiagnostic(#[label] pub Span);

#[derive(Debug, Default, Clone)]
pub struct RequirePropertyName;

declare_oxc_lint!(
/// ### What it does
/// Requires that all `@property` tags have names.
///
/// ### Why is this bad?
/// The name of a property type should be documented.
///
/// ### Example
/// ```javascript
/// // Passing
/// /**
/// * @typedef {SomeType} SomeTypedef
/// * @property {number} foo
/// */
///
/// // Failing
/// /**
/// * @typedef {SomeType} SomeTypedef
/// * @property {number}
/// */
/// ```
RequirePropertyName,
correctness
);

impl Rule for RequirePropertyName {
fn run_once(&self, ctx: &LintContext) {
let settings = &ctx.settings().jsdoc;
let resolved_property_tag_name = settings.resolve_tag_name("property");

for jsdoc in ctx.semantic().jsdoc().iter_all() {
for tag in jsdoc.tags() {
let tag_kind = tag.kind;

if tag_kind.parsed() != resolved_property_tag_name {
continue;
}
let (_, name_part, _) = tag.type_name_comment();
if name_part.is_some() {
continue;
};

ctx.diagnostic(RequirePropertyNameDiagnostic(tag_kind.span));
}
}
}
}

#[test]
fn test() {
use crate::tester::Tester;

let pass = vec![
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property foo
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property {string} foo
*/
",
None,
None,
),
(
"
/**
* @namespace {SomeType} SomeName
* @property {string} foo
*/
",
None,
None,
),
(
"
/**
* @class
* @property {string} foo
*/
",
None,
None,
),
];

let fail = vec![
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @property {string}
*/
",
None,
None,
),
(
"
/**
* @typedef {SomeType} SomeTypedef
* @prop {string}
*/
",
None,
Some(serde_json::json!({
"jsdoc": {
"tagNamePreference": {
"property": "prop",
},
},
})),
),
];

Tester::new(RequirePropertyName::NAME, pass, fail).test_and_snapshot();
}

0 comments on commit 5d89e75

Please sign in to comment.