From 7b43bb3cb1f3aa495249b9fb9260efce6566a580 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Tue, 5 Sep 2023 17:37:57 +0200 Subject: [PATCH] refactor(lint/noUselessConstructor): reduce false positives (#158) --- CHANGELOG.md | 3 + crates/rome_cli/tests/commands/check.rs | 20 ++--- crates/rome_cli/tests/commands/lint.rs | 11 ++- .../check_stdin_apply_successfully.snap | 5 +- ...in_apply_unsafe_only_organize_imports.snap | 4 +- ...check_stdin_apply_unsafe_successfully.snap | 5 +- .../check_stdin_apply_successfully.snap | 4 +- ...check_stdin_apply_unsafe_successfully.snap | 4 +- .../complexity/no_useless_constructor.rs | 78 ++++++++++++------- .../noUselessConstructor/invalid.ts | 5 ++ .../noUselessConstructor/invalid.ts.snap | 42 ++++++++++ .../noUselessConstructor/valid.jsonc.snap | 16 ++++ .../noUselessConstructor/validDecorator.js | 8 ++ .../validDecorator.js.snap | 18 +++++ .../validDecorator.options.json | 8 ++ crates/rome_js_syntax/src/parameter_ext.rs | 45 +++++++---- .../src/content/docs/internals/changelog.mdx | 3 + .../linter/rules/no-useless-constructor.md | 9 ++- 18 files changed, 222 insertions(+), 66 deletions(-) create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts.snap create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js.snap create mode 100644 crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.options.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d6491076f26e..984a2a22f0af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,9 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom + (a++) ** /**/ (2) ``` +- [noUselessConstructor](https://biomejs.dev/linter/rules/no-useless-constructor/) now ignores decorated classes and decorated parameters. + The rule now gives suggestions instead of safe fixes when parameters are annotated with types. + #### Bug fixes - Fix [#80](https://github.com/biomejs/biome/issues/95), making [noDuplicateJsxProps](https://biomejs.dev/linter/rules/no-duplicate-jsx-props/) case-insensitive. diff --git a/crates/rome_cli/tests/commands/check.rs b/crates/rome_cli/tests/commands/check.rs index dbb878187a2d..b0b68d2660d6 100644 --- a/crates/rome_cli/tests/commands/check.rs +++ b/crates/rome_cli/tests/commands/check.rs @@ -2220,7 +2220,7 @@ fn check_stdin_apply_successfully() { console .in_buffer - .push("function f() {return{}} class Foo { constructor() {} }".to_string()); + .push("import {a as a} from 'mod'; function f() {return{a}} class Foo {}".to_string()); let result = run_cli( DynRef::Borrowed(&mut fs), @@ -2239,7 +2239,10 @@ fn check_stdin_apply_successfully() { {message.content} }); - assert_eq!(content, "function f() {\n\treturn {};\n}\nclass Foo {}\n"); + assert_eq!( + content, + "import { a } from \"mod\";\nfunction f() {\n\treturn { a };\n}\nclass Foo {}\n" + ); assert_cli_snapshot(SnapshotPayload::new( module_path!(), @@ -2256,7 +2259,7 @@ fn check_stdin_apply_unsafe_successfully() { let mut console = BufferConsole::default(); console.in_buffer.push( - "import 'zod'; import 'lodash'; function f() {return{}} class Foo { constructor() {} }" + "import 'zod'; import 'lodash'; function f() {var x = 1; return{x}} class Foo {}" .to_string(), ); @@ -2288,7 +2291,7 @@ fn check_stdin_apply_unsafe_successfully() { assert_eq!( content, - "import \"lodash\";\nimport \"zod\";\nfunction f() {\n\treturn {};\n}\nclass Foo {}\n" + "import \"lodash\";\nimport \"zod\";\nfunction f() {\n\tconst x = 1;\n\treturn { x };\n}\nclass Foo {}\n" ); assert_cli_snapshot(SnapshotPayload::new( @@ -2305,10 +2308,9 @@ fn check_stdin_apply_unsafe_only_organize_imports() { let mut fs = MemoryFileSystem::default(); let mut console = BufferConsole::default(); - console.in_buffer.push( - "import 'zod'; import 'lodash'; function f() {return{}} class Foo { constructor() {} }" - .to_string(), - ); + console + .in_buffer + .push("import 'zod'; import 'lodash'; function f() {return{}} class Foo {}".to_string()); let result = run_cli( DynRef::Borrowed(&mut fs), @@ -2340,7 +2342,7 @@ fn check_stdin_apply_unsafe_only_organize_imports() { assert_eq!( content, - "import 'lodash'; import 'zod'; function f() {return{}} class Foo { constructor() {} }" + "import 'lodash'; import 'zod'; function f() {return{}} class Foo {}" ); assert_cli_snapshot(SnapshotPayload::new( diff --git a/crates/rome_cli/tests/commands/lint.rs b/crates/rome_cli/tests/commands/lint.rs index 06079793a329..554eab9c9bed 100644 --- a/crates/rome_cli/tests/commands/lint.rs +++ b/crates/rome_cli/tests/commands/lint.rs @@ -1875,7 +1875,7 @@ fn check_stdin_apply_successfully() { console .in_buffer - .push("function f() {return{}} class Foo { constructor() {} }".to_string()); + .push("import {a as a} from 'mod'; function f() {return{a}} class Foo {}".to_string()); let result = run_cli( DynRef::Borrowed(&mut fs), @@ -1894,7 +1894,10 @@ fn check_stdin_apply_successfully() { {message.content} }); - assert_eq!(content, "function f() {return{}} class Foo { }"); + assert_eq!( + content, + "import {a} from 'mod'; function f() {return{a}} class Foo {}" + ); assert_cli_snapshot(SnapshotPayload::new( module_path!(), @@ -1912,7 +1915,7 @@ fn check_stdin_apply_unsafe_successfully() { console .in_buffer - .push("function f() {return{}} class Foo { constructor() {} }".to_string()); + .push("function f() {var x=1; return{x}} class Foo {}".to_string()); let result = run_cli( DynRef::Borrowed(&mut fs), @@ -1939,7 +1942,7 @@ fn check_stdin_apply_unsafe_successfully() { {message.content} }); - assert_eq!(content, "function f() {return{}} class Foo { }"); + assert_eq!(content, "function f() {const x=1; return{x}} class Foo {}"); assert_cli_snapshot(SnapshotPayload::new( module_path!(), diff --git a/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_successfully.snap b/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_successfully.snap index 7cb515a3925f..3fb912206864 100644 --- a/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_successfully.snap +++ b/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_successfully.snap @@ -5,14 +5,15 @@ expression: content # Input messages ```block -function f() {return{}} class Foo { constructor() {} } +import {a as a} from 'mod'; function f() {return{a}} class Foo {} ``` # Emitted Messages ```block +import { a } from "mod"; function f() { - return {}; + return { a }; } class Foo {} diff --git a/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_only_organize_imports.snap b/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_only_organize_imports.snap index 9d383530d626..a10d5402eb23 100644 --- a/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_only_organize_imports.snap +++ b/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_only_organize_imports.snap @@ -5,13 +5,13 @@ expression: content # Input messages ```block -import 'zod'; import 'lodash'; function f() {return{}} class Foo { constructor() {} } +import 'zod'; import 'lodash'; function f() {return{}} class Foo {} ``` # Emitted Messages ```block -import 'lodash'; import 'zod'; function f() {return{}} class Foo { constructor() {} } +import 'lodash'; import 'zod'; function f() {return{}} class Foo {} ``` diff --git a/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_successfully.snap b/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_successfully.snap index b3fb5649f30e..a7e3b63f9b2e 100644 --- a/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_successfully.snap +++ b/crates/rome_cli/tests/snapshots/main_commands_check/check_stdin_apply_unsafe_successfully.snap @@ -5,7 +5,7 @@ expression: content # Input messages ```block -import 'zod'; import 'lodash'; function f() {return{}} class Foo { constructor() {} } +import 'zod'; import 'lodash'; function f() {var x = 1; return{x}} class Foo {} ``` # Emitted Messages @@ -14,7 +14,8 @@ import 'zod'; import 'lodash'; function f() {return{}} class Foo { constructor() import "lodash"; import "zod"; function f() { - return {}; + const x = 1; + return { x }; } class Foo {} diff --git a/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_successfully.snap b/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_successfully.snap index 41e040139a52..f3cffa2d0a56 100644 --- a/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_successfully.snap +++ b/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_successfully.snap @@ -5,13 +5,13 @@ expression: content # Input messages ```block -function f() {return{}} class Foo { constructor() {} } +import {a as a} from 'mod'; function f() {return{a}} class Foo {} ``` # Emitted Messages ```block -function f() {return{}} class Foo { } +import {a} from 'mod'; function f() {return{a}} class Foo {} ``` diff --git a/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_unsafe_successfully.snap b/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_unsafe_successfully.snap index 41e040139a52..02f74deda223 100644 --- a/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_unsafe_successfully.snap +++ b/crates/rome_cli/tests/snapshots/main_commands_lint/check_stdin_apply_unsafe_successfully.snap @@ -5,13 +5,13 @@ expression: content # Input messages ```block -function f() {return{}} class Foo { constructor() {} } +function f() {var x=1; return{x}} class Foo {} ``` # Emitted Messages ```block -function f() {return{}} class Foo { } +function f() {const x=1; return{x}} class Foo {} ``` diff --git a/crates/rome_js_analyze/src/analyzers/complexity/no_useless_constructor.rs b/crates/rome_js_analyze/src/analyzers/complexity/no_useless_constructor.rs index 0f105c1e5f3b..d0ce43e2f207 100644 --- a/crates/rome_js_analyze/src/analyzers/complexity/no_useless_constructor.rs +++ b/crates/rome_js_analyze/src/analyzers/complexity/no_useless_constructor.rs @@ -2,8 +2,8 @@ use rome_analyze::{context::RuleContext, declare_rule, ActionCategory, Ast, Rule use rome_console::markup; use rome_diagnostics::Applicability; use rome_js_syntax::{ - AnyJsCallArgument, AnyJsClass, AnyJsConstructorParameter, JsCallExpression, - JsConstructorClassMember, TsPropertyParameter, + AnyJsCallArgument, AnyJsClass, AnyJsConstructorParameter, AnyJsFormalParameter, + JsCallExpression, JsConstructorClassMember, }; use rome_rowan::{AstNode, AstNodeList, AstSeparatedList, BatchMutationExt}; @@ -27,7 +27,7 @@ declare_rule! { /// } /// ``` /// - /// ```js,expect_diagnostic + /// ```ts,expect_diagnostic /// class B extends A { /// constructor (a) { /// super(a); @@ -68,6 +68,13 @@ declare_rule! { /// constructor (private prop: number) {} /// } /// ``` + /// + /// ```ts + /// @Decorator + /// class C { + /// constructor (prop: number) {} + /// } + /// ``` pub(crate) NoUselessConstructor { version: "1.0.0", name: "noUselessConstructor", @@ -90,30 +97,35 @@ impl Rule for NoUselessConstructor { if is_not_public { return None; } - let has_param_property_or_default_param = constructor - .parameters() - .ok()? - .parameters() - .iter() - .filter_map(|x| x.ok()) - .any(|x| { - TsPropertyParameter::can_cast(x.syntax().kind()) - || x.as_any_js_formal_parameter() - .and_then(|x| x.as_js_formal_parameter()) - .and_then(|x| x.initializer()) - .is_some() - }); - if has_param_property_or_default_param { - return None; + for parameter in constructor.parameters().ok()?.parameters() { + let decorators = match parameter.ok()? { + AnyJsConstructorParameter::AnyJsFormalParameter( + AnyJsFormalParameter::JsBogusParameter(_), + ) + | AnyJsConstructorParameter::TsPropertyParameter(_) => { + // Ignore constructors with Bogus parameters or parameter properties + return None; + } + AnyJsConstructorParameter::AnyJsFormalParameter( + AnyJsFormalParameter::JsFormalParameter(parameter), + ) => parameter.decorators(), + AnyJsConstructorParameter::JsRestParameter(parameter) => parameter.decorators(), + }; + if !decorators.is_empty() { + // Ignore constructors with decorated parameters + return None; + } + } + let class = constructor.syntax().ancestors().find_map(AnyJsClass::cast); + if let Some(class) = &class { + if !class.decorators().is_empty() { + // Ignore decorated classes + return None; + } } - let has_parent_class = constructor - .syntax() - .ancestors() - .find_map(AnyJsClass::cast) - .filter(|x| x.extends_clause().is_some()) - .is_some(); let mut body_statements = constructor.body().ok()?.statements().iter(); let Some(first) = body_statements.next() else { + let has_parent_class = class.and_then(|x| x.extends_clause()).is_some(); if has_parent_class { // A `super` call is missing. // Do not report as useless constructor. @@ -160,11 +172,19 @@ impl Rule for NoUselessConstructor { let mut mutation = ctx.root().begin(); mutation.remove_node(constructor.clone()); // Safely remove the constructor whether there is no comments. - let applicability = if constructor.syntax().has_comments_descendants() { - Applicability::MaybeIncorrect - } else { - Applicability::Always - }; + let has_typed_parameters = constructor + .parameters() + .ok()? + .parameters() + .iter() + .find_map(|x| x.ok()?.type_annotation()) + .is_some(); + let applicability = + if has_typed_parameters || constructor.syntax().has_comments_descendants() { + Applicability::MaybeIncorrect + } else { + Applicability::Always + }; Some(JsRuleAction { category: ActionCategory::QuickFix, applicability, diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts new file mode 100644 index 000000000000..fd895a270566 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts @@ -0,0 +1,5 @@ +class B extends A { + constructor(foo: number) { + super(foo); + } +} diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts.snap b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts.snap new file mode 100644 index 000000000000..7ec6a88f0635 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/invalid.ts.snap @@ -0,0 +1,42 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalid.ts +--- +# Input +```js +class B extends A { + constructor(foo: number) { + super(foo); + } +} + +``` + +# Diagnostics +``` +invalid.ts:2:5 lint/complexity/noUselessConstructor FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This constructor is unnecessary. + + 1 │ class B extends A { + > 2 │ constructor(foo: number) { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 3 │ super(foo); + > 4 │ } + │ ^ + 5 │ } + 6 │ + + i Suggested fix: Remove the unnecessary constructor. + + 1 1 │ class B extends A { + 2 │ - ····constructor(foo:·number)·{ + 3 │ - ········super(foo); + 4 │ - ····} + 5 2 │ } + 6 3 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/valid.jsonc.snap b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/valid.jsonc.snap index 7bcbadce2101..f158a0d5a688 100644 --- a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/valid.jsonc.snap +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/valid.jsonc.snap @@ -12,6 +12,22 @@ class A { } class A { constructor(a, b = 0){ } } ``` +# Diagnostics +``` +valid.jsonc:1:11 lint/complexity/noUselessConstructor FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This constructor is unnecessary. + + > 1 │ class A { constructor(a, b = 0){ } } + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + + i Safe fix: Remove the unnecessary constructor. + + 1 │ class·A·{·constructor(a,·b·=·0){·}·} + │ ------------------------- + +``` + # Input ```js class A { constructor(){ doSomething(); } } diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js new file mode 100644 index 000000000000..95357e410a57 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js @@ -0,0 +1,8 @@ +class A { + constructor(@inject("foo") foo) {} +} + +@autoInjectable() +class B { + constructor(foo) {} +} diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js.snap b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js.snap new file mode 100644 index 000000000000..242d029ed0ac --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.js.snap @@ -0,0 +1,18 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: validDecorator.js +--- +# Input +```js +class A { + constructor(@inject("foo") foo) {} +} + +@autoInjectable() +class B { + constructor(foo) {} +} + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.options.json b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.options.json new file mode 100644 index 000000000000..5f88bf96ca19 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/complexity/noUselessConstructor/validDecorator.options.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "javascript": { + "parser": { + "unsafeParameterDecoratorsEnabled": true + } + } +} diff --git a/crates/rome_js_syntax/src/parameter_ext.rs b/crates/rome_js_syntax/src/parameter_ext.rs index 935aac5a0e30..c769a385cebe 100644 --- a/crates/rome_js_syntax/src/parameter_ext.rs +++ b/crates/rome_js_syntax/src/parameter_ext.rs @@ -1,7 +1,7 @@ use crate::{ AnyJsBindingPattern, AnyJsConstructorParameter, AnyJsFormalParameter, AnyJsParameter, JsConstructorParameterList, JsConstructorParameters, JsDecoratorList, JsLanguage, - JsParameterList, JsParameters, + JsParameterList, JsParameters, TsTypeAnnotation, }; use rome_rowan::{ declare_node_union, AstNodeList, AstSeparatedList, AstSeparatedListNodesIterator, SyntaxResult, @@ -181,7 +181,7 @@ impl AnyJsParameterList { /// /// # Returns /// - /// Returns true if the parameter list contains no parameters, false otherwise. + /// Returns `true` if the parameter list contains no parameters, false otherwise. pub fn is_empty(&self) -> bool { match self { AnyJsParameterList::JsParameterList(parameters) => parameters.is_empty(), @@ -410,10 +410,8 @@ impl AnyJsParameterList { pub fn has_any_decorated_parameter(&self) -> bool { self.iter().any(|parameter| { parameter.map_or(false, |parameter| match parameter { - AnyParameter::AnyJsConstructorParameter(parameter) => { - parameter.has_any_decorated_parameter() - } - AnyParameter::AnyJsParameter(parameter) => parameter.has_any_decorated_parameter(), + AnyParameter::AnyJsConstructorParameter(parameter) => parameter.has_any_decorator(), + AnyParameter::AnyJsParameter(parameter) => parameter.has_any_decorator(), }) }) } @@ -487,7 +485,7 @@ declare_node_union! { } impl AnyJsConstructorParameter { - /// This method returns a list of decorators for a parameter if it exists. + /// Returns the list of decorators of the parameter if the parameter is decorated. pub fn decorators(&self) -> Option { match self { AnyJsConstructorParameter::AnyJsFormalParameter(parameter) => parameter.decorators(), @@ -498,15 +496,28 @@ impl AnyJsConstructorParameter { } } - /// This method checks if any parameters in the given list are decorated. - pub fn has_any_decorated_parameter(&self) -> bool { + /// Returns `true` if any parameter in the given list is decorated. + pub fn has_any_decorator(&self) -> bool { self.decorators() .map_or(false, |decorators| !decorators.is_empty()) } + + /// Returns the type annotation of the parameter if any. + pub fn type_annotation(&self) -> Option { + match self { + AnyJsConstructorParameter::AnyJsFormalParameter(parameter) => { + parameter.type_annotation() + } + AnyJsConstructorParameter::JsRestParameter(parameter) => parameter.type_annotation(), + AnyJsConstructorParameter::TsPropertyParameter(parameter) => { + parameter.formal_parameter().ok()?.type_annotation() + } + } + } } impl AnyJsParameter { - /// This method returns a list of decorators for a parameter if it exists. + /// Returns the list of decorators of the parameter if the parameter is decorated. pub fn decorators(&self) -> Option { match self { AnyJsParameter::AnyJsFormalParameter(parameter) => parameter.decorators(), @@ -515,19 +526,27 @@ impl AnyJsParameter { } } - /// This method checks if any parameters in the given list are decorated. - pub fn has_any_decorated_parameter(&self) -> bool { + /// Returns `true` if any parameter in the given list is decorated. + pub fn has_any_decorator(&self) -> bool { self.decorators() .map_or(false, |decorators| !decorators.is_empty()) } } impl AnyJsFormalParameter { - /// This method returns a list of decorators for a parameter if it exists. + /// Returns the list of decorators of the parameter if the parameter is decorated. pub fn decorators(&self) -> Option { match self { AnyJsFormalParameter::JsBogusParameter(_) => None, AnyJsFormalParameter::JsFormalParameter(parameter) => Some(parameter.decorators()), } } + + /// Returns the type annotation of the parameter if any. + pub fn type_annotation(&self) -> Option { + match self { + AnyJsFormalParameter::JsBogusParameter(_) => None, + AnyJsFormalParameter::JsFormalParameter(parameter) => parameter.type_annotation(), + } + } } diff --git a/website/src/content/docs/internals/changelog.mdx b/website/src/content/docs/internals/changelog.mdx index fb2ecfcb73d8..c6846bbfe829 100644 --- a/website/src/content/docs/internals/changelog.mdx +++ b/website/src/content/docs/internals/changelog.mdx @@ -68,6 +68,9 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom + (a++) ** /**/ (2) ``` +- [noUselessConstructor](https://biomejs.dev/linter/rules/no-useless-constructor/) now ignores decorated classes and decorated parameters. + The rule now gives suggestions instead of safe fixes when parameters are annotated with types. + #### Bug fixes - Fix [#80](https://github.com/biomejs/biome/issues/95), making [noDuplicateJsxProps](https://biomejs.dev/linter/rules/no-duplicate-jsx-props/) case-insensitive. diff --git a/website/src/content/docs/linter/rules/no-useless-constructor.md b/website/src/content/docs/linter/rules/no-useless-constructor.md index a6247e0f31e3..91e5d779b06d 100644 --- a/website/src/content/docs/linter/rules/no-useless-constructor.md +++ b/website/src/content/docs/linter/rules/no-useless-constructor.md @@ -43,7 +43,7 @@ class A { -```jsx +```ts class B extends A { constructor (a) { super(a); @@ -132,6 +132,13 @@ class C { } ``` +```ts +@Decorator +class C { + constructor (prop: number) {} +} +``` + ## Related links - [Disable a rule](/linter/#disable-a-lint-rule)