diff --git a/.github/workflows/flutter-test.yaml b/.github/workflows/flutter-test.yaml index 9f12b27..5ff67b8 100644 --- a/.github/workflows/flutter-test.yaml +++ b/.github/workflows/flutter-test.yaml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: subosito/flutter-action@v2.18.0 + - uses: subosito/flutter-action@v2.21.0 with: channel: "stable" cache: true diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index e7d7883..eb5265e 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -3,15 +3,29 @@ name: Publish to pub.dev on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' # Matches v1.2.3 - - 'v[0-9]+.[0-9]+.[0-9]+\+[0-9]+' # Matches v1.2.3+1 + - "disco-[0-9]+.[0-9]+.[0-9]+" # Matches disco-1.2.3 + - 'disco-[0-9]+.[0-9]+.[0-9]+\+[0-9]+' # Matches disco-1.2.3+1 + - "disco_lint-[0-9]+.[0-9]+.[0-9]+" # Matches disco_lint-1.2.3 + - 'disco_lint-[0-9]+.[0-9]+.[0-9]+\+[0-9]+' # Matches disco_lint-1.2.3+1 # Publish using the reusable workflow from dart-lang. jobs: - publish: + publish-disco: + if: startsWith(github.ref, 'refs/tags/disco-') permissions: id-token: write # Required for authentication using OIDC - uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + uses: nank1ro/flutter-shadcn-ui/.github/workflows/cached-publish.yaml@main with: environment: pub.dev working-directory: packages/disco + install_flutter: true + + publish-disco-lint: + if: startsWith(github.ref, 'refs/tags/disco_lint-') + permissions: + id-token: write # Required for authentication using OIDC + uses: nank1ro/flutter-shadcn-ui/.github/workflows/cached-publish.yaml@main + with: + environment: pub.dev + working-directory: packages/disco_lint + install_flutter: false diff --git a/analysis_options.yaml b/analysis_options.yaml index ece596a..c273ffc 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -33,3 +33,8 @@ linter: # For additional information about configuring this file, see # https://dart.dev/guides/language/analysis-options + + +plugins: + disco_lint: + path: packages/disco_lint diff --git a/docs/src/content/docs/examples/auto-route.mdx b/docs/src/content/docs/examples/auto-route.mdx index 4b208b6..e279cac 100644 --- a/docs/src/content/docs/examples/auto-route.mdx +++ b/docs/src/content/docs/examples/auto-route.mdx @@ -17,7 +17,7 @@ This is the `auto_route` version used in this example: ```yaml {2} dependencies: - auto_route: ^9.3.0+1 + auto_route: ^11.0.0 ``` ## File structure diff --git a/docs/src/content/docs/installing.md b/docs/src/content/docs/installing.md index b49c2ee..c5fb614 100644 --- a/docs/src/content/docs/installing.md +++ b/docs/src/content/docs/installing.md @@ -21,7 +21,7 @@ Alternatively, you can add Disco manually by updating your `pubspec.yaml` file a name: # your app name environment: - sdk: ^3.6.0 # Dart SDK version must be >=3.6.0 to support disco + sdk: ^3.10.0 # Dart SDK version must be >=3.6.0 to support disco and >=3.10.0 to support disco_lint flutter: ">=3.27.0" dependencies: @@ -31,3 +31,15 @@ dependencies: ``` After updating the file, run `flutter pub get` in your terminal to fetch the dependencies. + +## Linter + +Disco provides an analyzer package called `disco_lint` to help you avoid common mistakes and simplify repetitive tasks (e.g. `Wrap with ProviderScope`). +Be sure to have the Dart SDK version `>= 3.10.0` and the Flutter SDK `>= 3.38.0`. + +Then edit your `analysis_options.yaml` file and add these lines of code: + +```yaml +plugins: + disco_lint: ^1.0.0 +``` diff --git a/examples/auto_route/pubspec.yaml b/examples/auto_route/pubspec.yaml index 37f56ad..6871ee8 100644 --- a/examples/auto_route/pubspec.yaml +++ b/examples/auto_route/pubspec.yaml @@ -4,21 +4,21 @@ publish_to: "none" version: 0.0.1 environment: - sdk: ^3.6.0 + sdk: ^3.10.0 resolution: workspace dependencies: - auto_route: ^9.3.0+1 + auto_route: ^11.0.0 disco: path: ../../packages/disco flutter: sdk: flutter dev_dependencies: - auto_route_generator: ^9.3.1 - build_runner: ^2.4.14 - very_good_analysis: ^9.0.0 + auto_route_generator: ^10.4.0 + build_runner: ^2.10.4 + very_good_analysis: ^10.0.0 flutter: uses-material-design: true diff --git a/examples/bloc/pubspec.yaml b/examples/bloc/pubspec.yaml index ff042a9..bc1409a 100644 --- a/examples/bloc/pubspec.yaml +++ b/examples/bloc/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 0.0.1 environment: - sdk: ^3.6.0 + sdk: ^3.10.0 resolution: workspace @@ -18,7 +18,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^9.0.0 + very_good_analysis: ^10.0.0 flutter: uses-material-design: true diff --git a/examples/preferences/pubspec.yaml b/examples/preferences/pubspec.yaml index b048ef3..9b45252 100644 --- a/examples/preferences/pubspec.yaml +++ b/examples/preferences/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: "none" version: 1.0.0+1 environment: - sdk: ^3.6.0 + sdk: ^3.10.0 resolution: workspace @@ -20,7 +20,7 @@ dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true diff --git a/examples/solidart/analysis_options.yaml b/examples/solidart/analysis_options.yaml index 0d94b99..2b3ec2d 100644 --- a/examples/solidart/analysis_options.yaml +++ b/examples/solidart/analysis_options.yaml @@ -3,8 +3,6 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: errors: always_put_required_named_parameters_first: ignore - plugins: - - custom_lint linter: rules: diff --git a/examples/solidart/pubspec.yaml b/examples/solidart/pubspec.yaml index 11c7cba..f7e5110 100644 --- a/examples/solidart/pubspec.yaml +++ b/examples/solidart/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" version: 0.0.1 environment: - sdk: ^3.6.0 + sdk: ^3.10.0 resolution: workspace @@ -17,11 +17,9 @@ dependencies: uuid: ^4.5.1 dev_dependencies: - custom_lint: ^0.7.0 flutter_test: sdk: flutter - solidart_lint: ^2.0.0 - very_good_analysis: ^9.0.0 + very_good_analysis: ^10.0.0 dependency_overrides: dart_style: ^3.0.1 diff --git a/packages/disco/CHANGELOG.md b/packages/disco/CHANGELOG.md index 612540c..1c74f8d 100644 --- a/packages/disco/CHANGELOG.md +++ b/packages/disco/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.0 + +- **FEAT**: Introduce the new `disco_lint` package to help avoid common mistakes and simplify repetitive tasks. + ## 1.0.3+1 - **CHORE**: Improve documentation. diff --git a/packages/disco/example/pubspec.yaml b/packages/disco/example/pubspec.yaml index a0acb3e..b1704d5 100644 --- a/packages/disco/example/pubspec.yaml +++ b/packages/disco/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" version: 0.0.1 environment: - sdk: ^3.6.0 + sdk: ^3.10.0 resolution: workspace @@ -17,7 +17,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^9.0.0 + very_good_analysis: ^10.0.0 flutter: uses-material-design: true diff --git a/packages/disco/pubspec.yaml b/packages/disco/pubspec.yaml index 32811e9..a64d731 100644 --- a/packages/disco/pubspec.yaml +++ b/packages/disco/pubspec.yaml @@ -10,7 +10,7 @@ topics: - provider environment: - sdk: ^3.6.0 + sdk: ^3.10.0 resolution: workspace @@ -23,4 +23,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.5 - very_good_analysis: ^9.0.0 + very_good_analysis: ^10.0.0 diff --git a/packages/disco_lint/.gitignore b/packages/disco_lint/.gitignore new file mode 100644 index 0000000..3a85790 --- /dev/null +++ b/packages/disco_lint/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/packages/disco_lint/.pubignore b/packages/disco_lint/.pubignore new file mode 100644 index 0000000..ee88966 --- /dev/null +++ b/packages/disco_lint/.pubignore @@ -0,0 +1 @@ +assets/ diff --git a/packages/disco_lint/CHANGELOG.md b/packages/disco_lint/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/disco_lint/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/disco_lint/README.md b/packages/disco_lint/README.md new file mode 100644 index 0000000..a965b4f --- /dev/null +++ b/packages/disco_lint/README.md @@ -0,0 +1,20 @@ +This package is a developer tool for users of disco, designed to help stop common issues and simplify repetitive tasks. + +> I highly recommend using this package to avoid errors and understand how to properly use disco + +## Getting started + +Be sure to have the Dart SDK version `>= 3.10.0` and the Flutter SDK `>= 3.38.0`. + +Then edit your `analysis_options.yaml` file and add these lines of code: + +```yaml +plugins: + disco_lint: ^1.0.0 +``` + +## ASSISTS + +### Wrap with ProviderScope + +![Wrap with ProviderScope sample](https://raw.githubusercontent.com/our-creativity/disco/main/packages/disco_lint/assets/wrap-with-providerscope.gif) diff --git a/packages/disco_lint/analysis_options.yaml b/packages/disco_lint/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/packages/disco_lint/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/disco_lint/assets/wrap-with-providerscope.gif b/packages/disco_lint/assets/wrap-with-providerscope.gif new file mode 100644 index 0000000..2c5f207 Binary files /dev/null and b/packages/disco_lint/assets/wrap-with-providerscope.gif differ diff --git a/packages/disco_lint/example/.gitignore b/packages/disco_lint/example/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/packages/disco_lint/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/disco_lint/example/.metadata b/packages/disco_lint/example/.metadata new file mode 100644 index 0000000..4c152b3 --- /dev/null +++ b/packages/disco_lint/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "66dd93f9a27ffe2a9bfc8297506ce066ff51265f" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f + base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f + - platform: macos + create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f + base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/disco_lint/example/README.md b/packages/disco_lint/example/README.md new file mode 100644 index 0000000..e9d9f7f --- /dev/null +++ b/packages/disco_lint/example/README.md @@ -0,0 +1,16 @@ +# lint_example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/disco_lint/example/analysis_options.yaml b/packages/disco_lint/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/packages/disco_lint/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/disco_lint/example/lib/main.dart b/packages/disco_lint/example/lib/main.dart new file mode 100644 index 0000000..1ba52c0 --- /dev/null +++ b/packages/disco_lint/example/lib/main.dart @@ -0,0 +1,71 @@ +import 'package:disco/disco.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return ProviderScope( + providers: [], + child: MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: .center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/packages/disco_lint/example/pubspec.yaml b/packages/disco_lint/example/pubspec.yaml new file mode 100644 index 0000000..8c3945c --- /dev/null +++ b/packages/disco_lint/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: lint_example +description: "A new Flutter project." +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ^3.10.0 + +resolution: workspace + +dependencies: + flutter: + sdk: flutter + disco: ^1.0.3+1 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true diff --git a/packages/disco_lint/example/test/widget_test.dart b/packages/disco_lint/example/test/widget_test.dart new file mode 100644 index 0000000..029e120 --- /dev/null +++ b/packages/disco_lint/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:lint_example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/packages/disco_lint/lib/disco_lint.dart b/packages/disco_lint/lib/disco_lint.dart new file mode 100644 index 0000000..316a905 --- /dev/null +++ b/packages/disco_lint/lib/disco_lint.dart @@ -0,0 +1 @@ +library; diff --git a/packages/disco_lint/lib/main.dart b/packages/disco_lint/lib/main.dart new file mode 100644 index 0000000..cb1f31c --- /dev/null +++ b/packages/disco_lint/lib/main.dart @@ -0,0 +1,18 @@ +import 'dart:async'; + +import 'package:disco_lint/src/assists/wrap_with_provider_scope.dart'; + +import 'package:analysis_server_plugin/plugin.dart'; +import 'package:analysis_server_plugin/registry.dart'; + +final plugin = _DiscoPlugin(); + +class _DiscoPlugin extends Plugin { + @override + String get name => 'disco_lint'; + + @override + FutureOr register(PluginRegistry registry) { + registry.registerAssist(WrapWithProviderScope.new); + } +} diff --git a/packages/disco_lint/lib/src/assists/base/wrap_builder.dart b/packages/disco_lint/lib/src/assists/base/wrap_builder.dart new file mode 100644 index 0000000..c9bd6d1 --- /dev/null +++ b/packages/disco_lint/lib/src/assists/base/wrap_builder.dart @@ -0,0 +1,82 @@ +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; +import 'package:analyzer/src/dart/ast/extensions.dart'; +import 'package:analyzer/src/dart/element/type.dart'; +import 'package:analyzer/src/utilities/extensions/flutter.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/range_factory.dart'; + +abstract class WrapBuilder extends ResolvedCorrectionProducer { + final List extraBuilderParams; + final List extraNamedParams; + final String builderName; + final String packageImport; + + WrapBuilder({ + required super.context, + required this.builderName, + required this.packageImport, + this.extraNamedParams = const [], + this.extraBuilderParams = const [], + }); + + @override + CorrectionApplicability get applicability => + CorrectionApplicability.singleLocation; + + bool canWrapOn(TypeImpl typeOrThrow) { + return !typeOrThrow.isExactWidgetTypeBuilder; + } + + @override + Future compute(ChangeBuilder builder) async { + final widgetExpr = node.findWidgetExpression; + if (widgetExpr == null) return; + if (!canWrapOn(widgetExpr.typeOrThrow)) return; + var widgetSrc = utils.getNodeText(widgetExpr); + + final builderElement = await sessionHelper.getClass( + packageImport, + builderName, + ); + + if (builderElement == null) return; + + final params = ['context', ...extraBuilderParams]; + + await builder.addDartFileEdit(file, (builder) { + builder.addReplacement(range.node(widgetExpr), (builder) { + builder.writeReference(builderElement); + + builder.writeln('('); + + final indentOld = utils.getLinePrefix(widgetExpr.offset); + final indentNew1 = indentOld + utils.oneIndent; + final indentNew2 = indentOld + utils.twoIndents; + + for (final namedParam in extraNamedParams) { + builder.write(indentNew1); + builder.write('$namedParam: '); + builder.addSimpleLinkedEdit('variable', namedParam); + builder.writeln(','); + } + + builder.write(indentNew1); + builder.writeln('builder: (${params.join(', ')}) {'); + + widgetSrc = utils.replaceSourceIndent(widgetSrc, indentOld, indentNew2); + builder.write(indentNew2); + builder.write('return $widgetSrc'); + builder.writeln(';'); + + builder.write(indentNew1); + var addTrailingCommas = getCodeStyleOptions( + unitResult.file, + ).addTrailingCommas; + builder.writeln('}${addTrailingCommas ? "," : ""}'); + + builder.write(indentOld); + builder.write(')'); + }); + }); + } +} diff --git a/packages/disco_lint/lib/src/assists/base/wrap_single_widget.dart b/packages/disco_lint/lib/src/assists/base/wrap_single_widget.dart new file mode 100644 index 0000000..0b9e411 --- /dev/null +++ b/packages/disco_lint/lib/src/assists/base/wrap_single_widget.dart @@ -0,0 +1,73 @@ +import 'package:analysis_server_plugin/edit/dart/correction_producer.dart'; +import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart'; +import 'package:analyzer_plugin/utilities/range_factory.dart'; +// ignore: implementation_imports +import 'package:analyzer/src/utilities/extensions/flutter.dart'; + +/// A correction processor that can make one of the possible changes computed by +/// the [FlutterWrap] producer. +abstract class WrapSingleWidget extends ResolvedCorrectionProducer { + WrapSingleWidget({ + required super.context, + required this.widgetName, + required this.packageImport, + this.extraNamedParams = const [], + }); + + final String packageImport; + final String widgetName; + final List extraNamedParams; + + @override + CorrectionApplicability get applicability => + CorrectionApplicability.singleLocation; + + @override + Future compute(ChangeBuilder builder) async { + final widgetExpr = node.findWidgetExpression; + if (widgetExpr == null) return; + var widgetSrc = utils.getNodeText(widgetExpr); + + final widgetElement = await sessionHelper.getClass( + packageImport, + widgetName, + ); + + if (widgetElement == null) return; + + await builder.addDartFileEdit(file, (builder) { + var eol = builder.eol; + builder.addReplacement(range.node(widgetExpr), (builder) { + builder.writeReference(widgetElement); + builder.write('('); + // When there's no linked edit for the widget name, leave the selection + // inside the opening paren which is useful if you want to add + // additional named arguments to the newly-created widget. + builder.selectHere(); + if (widgetSrc.contains(eol) || extraNamedParams.isNotEmpty) { + var indentOld = utils.getLinePrefix(widgetExpr.offset); + var indentNew = '$indentOld${utils.oneIndent}'; + + for (final namedParam in extraNamedParams) { + builder.writeln(); + builder.write(indentNew); + builder.write(namedParam); + } + + builder.writeln(); + builder.write(indentNew); + widgetSrc = utils.replaceSourceIndent( + widgetSrc, + indentOld, + indentNew, + ); + widgetSrc += ',$eol$indentOld'; + } + builder.write('child'); + builder.write(': '); + builder.write(widgetSrc); + builder.write(')'); + }); + }); + } +} diff --git a/packages/disco_lint/lib/src/assists/wrap_with_provider_scope.dart b/packages/disco_lint/lib/src/assists/wrap_with_provider_scope.dart new file mode 100644 index 0000000..9dfe0a8 --- /dev/null +++ b/packages/disco_lint/lib/src/assists/wrap_with_provider_scope.dart @@ -0,0 +1,18 @@ +import 'package:analyzer_plugin/utilities/assist/assist.dart'; +import 'package:disco_lint/src/assists/base/wrap_single_widget.dart'; + +class WrapWithProviderScope extends WrapSingleWidget { + WrapWithProviderScope({required super.context}) + : super( + widgetName: 'ProviderScope', + extraNamedParams: const ['providers: [],'], + packageImport: 'package:disco/disco.dart', + ); + + @override + AssistKind get assistKind => const AssistKind( + 'disco.wrap_with_provider_scope', + 30, + 'Wrap with ProviderScope', + ); +} diff --git a/packages/disco_lint/pubspec.yaml b/packages/disco_lint/pubspec.yaml new file mode 100644 index 0000000..5c1f375 --- /dev/null +++ b/packages/disco_lint/pubspec.yaml @@ -0,0 +1,24 @@ +name: disco_lint +description: disco_lint is a developer tool for users of disco, designed to help stop common issues and simplify repetitive tasks +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.10.3 + +resolution: workspace + +dependencies: + analyzer: ^8.0.0 + analyzer_plugin: ^0.13.10 + analysis_server_plugin: ^0.3.3 + collection: ^1.19.1 + meta: ^1.17.0 + path: ^1.9.1 + source_span: ^1.10.1 + build: ^4.0.3 + yaml: ^3.1.3 + +dev_dependencies: + lints: ^6.0.0 + test: ^1.25.2 diff --git a/pubspec.yaml b/pubspec.yaml index 4720d26..3e1f1e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,12 @@ name: _ publish_to: none environment: - sdk: ^3.6.0 + sdk: ^3.10.0 workspace: - packages/disco - packages/disco/example + - packages/disco_lint + - packages/disco_lint/example - examples/auto_route - examples/bloc - examples/solidart