diff --git a/CHANGELOG.md b/CHANGELOG.md index 813003b..a66abcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.6.0 + +#### Breaking + +- **Removed `DARTANALYZER` analyzer mode.** The `dartanalyzer` command was deprecated in Dart 2.18 and removed in Dart 3. Use `DART` or `FLUTTER` mode instead. The `sonar.dart.analyzer.mode` option no longer accepts `DARTANALYZER`. + +#### Enhancements + +- **Dart 3.x linter rules:** Added 14 new rules — `avoid_futureor_void`, `omit_obvious_property_types`, `remove_deprecations_in_breaking_versions`, `simplify_variable_pattern`, `specify_nonobvious_local_variable_types`, `specify_nonobvious_property_types`, `strict_top_level_inference`, `switch_on_type`, `unnecessary_async`, `unnecessary_ignore`, `unnecessary_unawaited`, `unnecessary_underscores`, `unsafe_variance`, `use_null_aware_elements` +- **Deprecated rules deactivated:** 13 rules removed in Dart 3 are now `active: false` — `always_require_non_null_named_parameters`, `avoid_as`, `avoid_returning_null`, `avoid_returning_null_for_future`, `invariant_booleans`, `prefer_bool_in_asserts`, `prefer_equal_for_default_values`, `super_goes_last`, `package_api_docs`, `enable_null_safety`, `deprecated_new_in_comment_reference`, `type_check_with_null`, `unsafe_html` +- **ANTLR grammar updated for Dart 3:** `sealed`, `base`, `final`, `interface`, `mixin` class modifiers, record types/literals, switch expressions, pattern matching (type/variable/wildcard/constant patterns), if-case statements, `when` guard clauses, nullable type syntax +- **State management detection:** PubSpec parser now detects BLoC (`flutter_bloc`, `bloc`), Riverpod (`flutter_riverpod`, `hooks_riverpod`, `riverpod`), and Provider dependencies +- **24 new state management rules:** 9 BLoC rules + 15 Riverpod rules with MQR clean code attributes, registered under `flutter-state-management` repository +- **New Dart keywords:** `base`, `sealed`, `when` added to syntax highlighter +- **Total rules: 329** (305 dartanalyzer + 24 state management) + +#### Bug Fixes + +- None. + ## 0.5.2 #### Breaking diff --git a/README.md b/README.md index 3b1d383..9981662 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,163 @@ - +# SonarQube Plugin for Flutter / Dart -[](https://sonarcloud.io/summary/new_code?id=insideapp-oss_sonar-flutter) -[](https://sonarcloud.io/summary/new_code?id=insideapp-oss_sonar-flutter) -[](https://sonarcloud.io/summary/new_code?id=insideapp-oss_sonar-flutter) -[](https://sonarcloud.io/summary/new_code?id=insideapp-oss_sonar-flutter) -[](https://sonarcloud.io/summary/new_code?id=insideapp-oss_sonar-flutter) +> Static code analysis for Dart and Flutter projects in SonarQube — with Dart 3.x support, 329 rules, and state management linting. -# SonarQube plugin for Flutter / Dart +
+
+
-
-
DETECT|DART|FLUTTER|MANUAL|DARTANALYZER | `DETECT` | By default the plugin attempts to detect a fitting analyzer (`flutter analyze` or `dart analyze`) by parsing the `environment` from `pubspec.yaml`. This can be set to `MANUAL` to provide and existing report file. For compatibility with older Dart versions, this can be set to `DARTANALYZER`. |
-| `sonar.dart.analyzer.options.override` | true|false | `true` | By default any local `analysis_options.yaml` will be replaced for the analysis. This can be prevented by setting this to `false`. |
-| `sonar.dart.analyzer.report.mode` | DETECT|MACHINE|LEGACY | `DETECT` | The new machine readable output can be automatically detected if Dart SDK is available on the $PATH. |
-| `sonar.dart.analyzer.report.path` | A file path | - | This is required if the analyzer mode is set to `MANUAL`. |
-| `sonar.flutter.tests.reportPath` | Comma separated list of file paths (wildcard not supported) | `tests.output` | The path to the test report JSON file. |
-| `sonar.flutter.coverage.reportPath` | A file path | `coverage/lcov.info` | The path to the test coverage file in LCOV format. |
+# Run tests
+mvn test
+# Build plugin JAR
+mvn clean package
+# Output: sonar-flutter-plugin/target/sonar-flutter-plugin-*.jar
+```
-## Contributing
+**Requirements:** Java 17+, Maven 3.8+
-Any help is welcome, and PRs will be greatly appreciated!
+---
-Please read [CONTRIBUTING](https://github.com/insideapp-oss/sonar-flutter/blob/develop/CONTRIBUTING.md) for more information.
+## Contributing
+PRs are welcome! Please read [CONTRIBUTING](CONTRIBUTING.md) for guidelines.
## License
-SonarQube Plugin for Flutter / Dart is released under the GNU LGPL v3 license. See the [LICENSE](https://github.com/insideapp-oss/sonar-flutter/blob/develop/LICENSE) file for more information.
+GNU LGPL v3 — see [LICENSE](LICENSE) for details.
+
+---
+
+*Forked from [insideapp-oss/sonar-flutter](https://github.com/insideapp-oss/sonar-flutter). Extended with Dart 3.x support, state management rules, and MQR mode.*
diff --git a/dart-lang/src/main/antlr/Dart2.g4 b/dart-lang/src/main/antlr/Dart2.g4
index f9432a0..fbbd5cb 100644
--- a/dart-lang/src/main/antlr/Dart2.g4
+++ b/dart-lang/src/main/antlr/Dart2.g4
@@ -137,9 +137,19 @@ defaultNamedParameter
| normalFormalParameter (':' expression)?
;
-// 10 Classes
+// 10 Classes (updated for Dart 3 class modifiers)
+classModifier
+ : 'sealed'
+ | 'base'
+ | 'final'
+ | 'interface'
+ | 'mixin'
+ ;
classDefinition
- : metadata 'abstract'? 'class' identifier typeParameters?
+ : metadata classModifier* 'abstract'? 'class' identifier typeParameters?
+ superclass? mixins? interfaces?
+ '{' (metadata classMemberDefinition)* '}'
+ | metadata 'abstract'? classModifier* 'class' identifier typeParameters?
superclass? mixins? interfaces?
'{' (metadata classMemberDefinition)* '}'
| metadata 'abstract'? 'class' mixinApplicationClass
@@ -297,9 +307,61 @@ primary
| identifier
| nayaExpression
| constObjectExpression
+ | recordLiteral
+ | switchExpression
| '(' expression ')'
;
+// Dart 3 Records
+recordLiteral
+ : '(' recordField ',' recordField (',' recordField)* ','? ')'
+ ;
+recordField
+ : (identifier ':')? expression
+ ;
+recordType
+ : '(' recordTypeField ',' recordTypeField (',' recordTypeField)* ','? ')'
+ ;
+recordTypeField
+ : (identifier ':')? dtype
+ ;
+
+// Dart 3 Switch Expressions
+switchExpression
+ : 'switch' '(' expression ')' '{' switchExpressionCase (',' switchExpressionCase)* ','? '}'
+ ;
+switchExpressionCase
+ : guardedPattern '=>' expression
+ ;
+guardedPattern
+ : pattern ('when' expression)?
+ ;
+pattern
+ : constantPattern
+ | typeTestPattern
+ | wildcardPattern
+ | variablePattern
+ | parenthesizedPattern
+ ;
+constantPattern
+ : literal
+ | identifier
+ | qualified
+ | constObjectExpression
+ ;
+typeTestPattern
+ : dtype identifier
+ ;
+wildcardPattern
+ : '_'
+ ;
+variablePattern
+ : ('var' | 'final' dtype? ) identifier
+ ;
+parenthesizedPattern
+ : '(' pattern ')'
+ ;
+
// 16.1 Constants
literal
@@ -702,9 +764,10 @@ localVariableDeclaration
localFunctionDeclaration
: functionSignature functionBody
;
-// 17.5 If
+// 17.5 If (updated for Dart 3 if-case)
ifStatement
: 'if' '(' expression ')' statement ('else' statement)?
+ | 'if' '(' expression 'case' pattern ('when' expression)? ')' statement ('else' statement)?
;
// 17.6 For for
@@ -730,12 +793,12 @@ whileStatement
doStatement
: 'do' statement 'while' '(' expression ')' ';'
;
-// 17.9 Switch
+// 17.9 Switch (updated for Dart 3 patterns and when guards)
switchStatement
: 'switch' '(' expression ')' '{' switchCase* defaultCase? '}'
;
switchCase
- : label* 'case' expression ':' statements
+ : label* 'case' expression ('when' expression)? ':' statements
;
defaultCase
: label* 'default' ':' statements
@@ -885,7 +948,8 @@ uriTest
// 19.1 Static Types
dtype
- : typeName typeArguments?
+ : typeName typeArguments? '?'?
+ | recordType '?'?
;
typeName
: qualified
diff --git a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/DartSensor.java b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/DartSensor.java
index 31a9a9f..99693c9 100644
--- a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/DartSensor.java
+++ b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/DartSensor.java
@@ -31,15 +31,15 @@
import fr.insideapp.sonarqube.dart.lang.antlr.HighlighterVisitor;
import fr.insideapp.sonarqube.dart.lang.antlr.ParseTreeItemVisitor;
import fr.insideapp.sonarqube.dart.lang.antlr.SourceLinesVisitor;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
public class DartSensor implements Sensor {
- private static final Logger LOGGER = Loggers.get(DartSensor.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(DartSensor.class);
@Override
public void describe(@Nonnull SensorDescriptor sensorDescriptor) {
diff --git a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/KeywordsProvider.java b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/KeywordsProvider.java
index 600a2fb..a58e4a1 100644
--- a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/KeywordsProvider.java
+++ b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/KeywordsProvider.java
@@ -17,9 +17,9 @@
*/
package fr.insideapp.sonarqube.dart.lang;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStream;
@@ -28,7 +28,7 @@
import java.util.List;
public class KeywordsProvider {
- private static final Logger LOGGER = Loggers.get(KeywordsProvider.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(KeywordsProvider.class);
private final ListThe default implementation does nothing.
*/ @Override public void exitDefaultNamedParameter(Dart2Parser.DefaultNamedParameterContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterClassModifier(Dart2Parser.ClassModifierContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitClassModifier(Dart2Parser.ClassModifierContext ctx) { } /** * {@inheritDoc} * @@ -711,6 +707,162 @@ public class Dart2BaseListener implements Dart2Listener { *The default implementation does nothing.
*/ @Override public void exitPrimary(Dart2Parser.PrimaryContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterRecordLiteral(Dart2Parser.RecordLiteralContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitRecordLiteral(Dart2Parser.RecordLiteralContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterRecordField(Dart2Parser.RecordFieldContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitRecordField(Dart2Parser.RecordFieldContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterRecordType(Dart2Parser.RecordTypeContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitRecordType(Dart2Parser.RecordTypeContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterRecordTypeField(Dart2Parser.RecordTypeFieldContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitRecordTypeField(Dart2Parser.RecordTypeFieldContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterSwitchExpression(Dart2Parser.SwitchExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitSwitchExpression(Dart2Parser.SwitchExpressionContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterSwitchExpressionCase(Dart2Parser.SwitchExpressionCaseContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitSwitchExpressionCase(Dart2Parser.SwitchExpressionCaseContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterGuardedPattern(Dart2Parser.GuardedPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitGuardedPattern(Dart2Parser.GuardedPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterPattern(Dart2Parser.PatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitPattern(Dart2Parser.PatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterConstantPattern(Dart2Parser.ConstantPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitConstantPattern(Dart2Parser.ConstantPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterTypeTestPattern(Dart2Parser.TypeTestPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitTypeTestPattern(Dart2Parser.TypeTestPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterWildcardPattern(Dart2Parser.WildcardPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitWildcardPattern(Dart2Parser.WildcardPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterVariablePattern(Dart2Parser.VariablePatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitVariablePattern(Dart2Parser.VariablePatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void enterParenthesizedPattern(Dart2Parser.ParenthesizedPatternContext ctx) { } + /** + * {@inheritDoc} + * + *The default implementation does nothing.
+ */ + @Override public void exitParenthesizedPattern(Dart2Parser.ParenthesizedPatternContext ctx) { } /** * {@inheritDoc} * diff --git a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2BaseVisitor.java b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2BaseVisitor.java index adc20b8..0672208 100644 --- a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2BaseVisitor.java +++ b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2BaseVisitor.java @@ -1,21 +1,5 @@ -/* - * SonarQube Flutter Plugin - Enables analysis of Dart and Flutter projects into SonarQube. - * Copyright © 2020 inside|app (contact@insideapp.fr) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, seeThe default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitClassModifier(Dart2Parser.ClassModifierContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * @@ -426,6 +417,97 @@ public class Dart2BaseVisitorThe default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitRecordLiteral(Dart2Parser.RecordLiteralContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitRecordField(Dart2Parser.RecordFieldContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitRecordType(Dart2Parser.RecordTypeContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitRecordTypeField(Dart2Parser.RecordTypeFieldContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitSwitchExpression(Dart2Parser.SwitchExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitSwitchExpressionCase(Dart2Parser.SwitchExpressionCaseContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitGuardedPattern(Dart2Parser.GuardedPatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitPattern(Dart2Parser.PatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitConstantPattern(Dart2Parser.ConstantPatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitTypeTestPattern(Dart2Parser.TypeTestPatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitWildcardPattern(Dart2Parser.WildcardPatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitVariablePattern(Dart2Parser.VariablePatternContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.
+ */ + @Override public T visitParenthesizedPattern(Dart2Parser.ParenthesizedPatternContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * diff --git a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2Lexer.java b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2Lexer.java index 4fa3f34..e39d3c7 100644 --- a/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2Lexer.java +++ b/dart-lang/src/main/java/fr/insideapp/sonarqube/dart/lang/antlr/generated/Dart2Lexer.java @@ -1,28 +1,13 @@ -/* - * SonarQube Flutter Plugin - Enables analysis of Dart and Flutter projects into SonarQube. - * Copyright © 2020 inside|app (contact@insideapp.fr) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see\n AVOID\n lines longer than 80 characters\n
\nReadability studies show that long lines of text are harder to read because your eye has to travel farther when moving to the beginning of the next line. This is why newspapers and magazines use multiple columns of text.
\n\n If you really find yourself wanting lines longer than 80 characters, our experience is that your code is likely too verbose and could be a little more compact. The main offender is usually \n VeryLongCamelCaseClassNames\n . Ask yourself, \u201cDoes each word in that type name tell me something critical or prevent a name collision?\u201d If not, consider omitting it.\n
VeryLongCamelCaseClassNames\n\n Note that \n dart format\n does 99% of this for you, but the last 1% is you. It does not split long string literals to fit in 80 columns, so you have to do that manually.\n
dart format\nWe make an exception for URIs and file paths. When those occur in comments or strings (usually in imports and exports), they may remain on a single line even if they go over the line limit. This makes it easier to search source files for a given path.
\n\n AVOID\n lines longer than 80 characters\n
\nReadability studies show that long lines of text are harder to read because your eye has to travel farther when moving to the beginning of the next line. This is why newspapers and magazines use multiple columns of text.
\n\n If you really find yourself wanting lines longer than 80 characters, our experience is that your code is likely too verbose and could be a little more compact. The main offender is usually \n VeryLongCamelCaseClassNames\n . Ask yourself, “Does each word in that type name tell me something critical or prevent a name collision?” If not, consider omitting it.\n
VeryLongCamelCaseClassNames\n\n Note that \n dart format\n does 99% of this for you, but the last 1% is you. It does not split long string literals to fit in 80 columns, so you have to do that manually.\n
dart format\nWe make an exception for URIs and file paths. When those occur in comments or strings (usually in imports and exports), they may remain on a single line even if they go over the line limit. This makes it easier to search source files for a given path.
\n\n DO\n provide doc comments for all public APIs.\n
\n\n As described in the \n pub package layout doc\n , public APIs consist in everything in your package's \n lib\n folder, minus implementation files in \n lib/src\n , adding elements explicitly exported with an \n export\n directive.\n
lib\nlib/src\nexport\n\n For example, given \n lib/foo.dart\n :\n
lib/foo.dart\n\n \n export\n 'src/bar.dart'\n show\n Bar;\n \n \n export\n 'src/baz.dart'\n ;\n \n \n \n class\n Foo\n { }\n \n \n \n class\n _Foo\n { }\n \n\nits API includes:
\nFoo\n_Foo\nBar\nsrc/baz.dart\n\n All public API members should be documented with \n ///\n doc-style comments.\n
///\n\n BAD:\n
\n\n \n class\n Bar\n {\n \n \n void\n bar\n ();\n \n \n }\n \n\n\n GOOD:\n
\n\n \n /// A Foo.\n \n \n abstract\n class\n Foo\n {\n \n \n /// Start foo-ing.\n \n \n void\n start\n () => \n _start\n ();\n \n \n \n _start\n ();\n \n \n }\n \n\n\n Advice for writing good doc comments can be found in the \n Doc Writing Guidelines\n .\n
\n\n AVOID\n
\nhref\nsrc\nsrcdoc\ncreateFragment\nopen\nsetInnerHtml\nElement.html\nDocumentFragment.html\n\n BAD:\n
\n\n \n var\n script = \n ScriptElement\n ()..src = \n 'foo.js'\n ;\n \n\nAVOID using FutureOr<void> as the type of a result. This type is problematic because it may appear to encode that a result is either a Future<void> or void, but it actually just means void. Use Future<void>? to encode a result that is either a Future<void> or null.
DON'T type annotate initialized properties when the type is obvious. If a property is initialized to a literal, a constructor invocation, or a static method invocation of the same type, the type annotation is unnecessary.
DO remove deprecated members when a breaking version increment is made. Keeping deprecated members around increases the maintenance cost and API surface of your codebase.
DO use simpler variable patterns where possible. In Dart 3, patterns allow destructuring and binding, but some patterns can be simplified for readability.
DO specify the type of a local variable when it is not obvious from the initializer. When the type of a local variable is not obvious from the initializer expression, specify the type explicitly.
DO specify the type of a property when it is not obvious from the initializer. When the type of a property is not obvious from the initializer expression, specify the type explicitly for better readability.
DO add explicit type annotations for top-level declarations when the type cannot be inferred. Top-level declarations without explicit types make it harder for readers and tools to understand code.
DO use switch on types instead of chains of is checks. In Dart 3, you can use switch expressions with type patterns instead of a long chain of if-else with is checks.
AVOID marking a function as async when it has no meaningful await expressions. Adding async to a function that doesn't use await is unnecessary and adds overhead.
AVOID unnecessary // ignore: comments. If the diagnostic being ignored is not actually produced, the ignore comment is unnecessary and should be removed.
AVOID wrapping a non-Future expression in unawaited(). The unawaited() function is intended to mark Futures that are intentionally not awaited. Wrapping non-Future expressions is unnecessary.
AVOID unnecessary underscores in variable names. In Dart 3, wildcard variables use a single underscore _. Multiple underscores like __ or ___ are unnecessary.
AVOID using the @unsafeVariance annotation to suppress variance-related errors. Using this annotation can lead to runtime type errors that the type system would normally prevent.
DO use null-aware elements in collections. In Dart 3, you can use ?element in list and set literals to conditionally include elements that might be null.
Avoid importing Flutter packages in BLoC classes. BLoC logic should be platform-independent and not depend on Flutter framework.
Avoid adding public methods to BLoC classes other than close(). Events should be the only way to communicate with a BLoC from the presentation layer.
Consider using BLoC instead of Cubit when the component has complex event-driven logic. BLoC provides better traceability of state changes through events.
Consider using Cubit instead of BLoC when the component has simple state transitions without complex event handling.
BLoC should not return or contain widget references. BLoC handles business logic only; the UI layer should build widgets based on state.
Avoid directly depending on one BLoC inside another. Use events or a shared repository layer to coordinate between BLoCs.
Prefer using emit() instead of yield for state transitions in modern BLoC. The yield-based API is deprecated.
BLoC state classes should be immutable. Use final fields and create new state instances for transitions.
BLoC instances should be properly closed when no longer needed to prevent memory leaks. Use BlocProvider for automatic lifecycle management.
Riverpod requires a ProviderScope widget at the root of the widget tree. Without it, providers will not work correctly.
Providers that depend on other providers should explicitly declare their dependencies using the @Riverpod annotation's dependencies parameter.
Providers should not depend on BuildContext. This creates tight coupling between the provider and the widget tree, making testing and reuse difficult.
When using Riverpod code generation, prefer @riverpod annotation over manually creating Provider/StateProvider instances.
Ensure Notifier classes extend the correct base class. AsyncNotifier for async state, Notifier for sync state.
Provider declarations should be final to prevent accidental reassignment.
Avoid using ref.read inside the build method. Use ref.watch instead to ensure the widget rebuilds when the provider state changes.
Avoid using ref.watch outside the build method. Use ref.read for one-time reads in callbacks and event handlers.
Prefer extending ConsumerWidget instead of using a Consumer widget inside a StatelessWidget for cleaner code.
Consider using autoDispose modifier for providers that are scoped to specific pages or features to prevent memory leaks.
When consuming AsyncValue from an async provider, always handle error and loading states in addition to data.
Use Provider instead of StateProvider or StateNotifierProvider when the provider does not need to manage mutable state.
Avoid using global mutable variables accessed by providers. Use proper state management through Riverpod's state mechanisms.
When using code generation, ensure the ref parameter is correctly typed as the generated Ref type.
Notifier properties should be private. Expose state changes only through the state property and public methods.