Giter Club home page Giter Club logo

dart_pre_commit's Introduction

dart_pre_commit

Continuos Integration Pub Version

A small collection of pre commit hooks to format and lint dart code

Table of Contents

Table of contents generated with markdown-toc

Features

  • Provides multiple built in hooks to run on staged files
    • Run dart format
    • Check for invalid imports in test files
    • Run dart analyze
    • Ensure all src files that are publicly visible are exported somewhere
    • Checks if a dart package is compatible with the current stable flutter version
    • Checks if any packages are outdated
    • Checks if any packages have newer versions in the lock file
  • Only processes staged files
    • Automatically stages modified files again
    • Fails if partially staged files had to be modified
  • Can be used as binary or as library
  • Integrates well with most git hook solutions

Installation

Simply add dart_pre_commit to your pubspec.yaml (preferably as dev dependency) and run dart pub get (or flutter pub get).

dart pub add --dev dart_pre_commit

Activation

To make use of the hooks, you have to activate them first. This package only comes with the hook-code itself, not with a way to integrate it with git as actual hook. Here are a few examples on how to do so:

Simple dart wrapper

If this is the only hook you need and you don't really need anything more then "just run the thing", you can simply create a file named tool/setup_git_hooks.dart as detailed below and run dart run tool/setup_git_hooks.dart to initialize the hooks on each of your machines.

import 'dart:io';

Future<void> main() async {
  final preCommitHook = File('.git/hooks/pre-commit');
  await preCommitHook.parent.create();
  await preCommitHook.writeAsString(
    '''
#!/bin/sh
exec dart run dart_pre_commit # specify custom options here
# exec flutter pub run dart_pre_commit # Use this instead when working on a flutter project
''',
  );

  if (!Platform.isWindows) {
    final result = await Process.run('chmod', ['a+x', preCommitHook.path]);
    stdout.write(result.stdout);
    stderr.write(result.stderr);
    exitCode = result.exitCode;
  }
}

When using FVM

When using FVM, you may want to adjust the dart script from above to run dart_pre_commit via FVM:

import 'dart:io';

Future<void> main() async {
  // Add this part
  final useFvm = !arguments.contains('--no-fvm');
  final command = useFvm
      ? 'fvm dart run dart_pre_commit'  // or "fmv flutter pub run dart_pre_commit" for flutter projects
      : 'dart run dart_pre_commit';  // or "flutter pub run dart_pre_commit" for flutter projects

  final preCommitHook = File('.git/hooks/pre-commit');
  await preCommitHook.parent.create();
  await preCommitHook.writeAsString(
    '''
#!/bin/sh
exec $command # use the previously selected command here
''',
  );

  if (!Platform.isWindows) {
    final result = await Process.run('chmod', ['a+x', preCommitHook.path]);
    stdout.write(result.stdout);
    stderr.write(result.stderr);
    exitCode = result.exitCode;
  }
}

Using git_hooks

The second example uses the git_hooks package to activate the hook. Take the following steps to activate the hook:

  1. Add git_hooks as dev dependency to your project
  2. Follow the installation instructions here: https://pub.dev/packages/git_hooks#create-files-in-githooks
  3. Modify bin/git_hooks.dart to look like the following:
import "package:dart_pre_commit/dart_pre_commit.dart";
import "package:git_hooks/git_hooks.dart";

void main(List<String> arguments) {
  final params = {
    Git.preCommit: _preCommit
  };
  GitHooks.call(arguments, params);
}

Future<bool> _preCommit() async {
  final result = await DartPreCommit.run();
  return result.isSuccess;
}

Handling the case where the git_hooks is setup in a child folder of the repository

In cases where your project is in a child folder of the repository ie. Repo/project, when you run DartPreCommit.run(), it'll run from the root of your repository and hence will not be able to find pubspec.yaml file. To handle this case we need to direct the DartPreCommit to run its command from a child folder. To do so, you need to add the following line in the _preCommit() function before we invoke DartPreCommit:

Future<bool> _preCommit() async {
  Directory.current = '/project_sub_directory'; // <--- This line switches the scan directory to a subdirectory
  final result = await DartPreCommit.run();
  return result.isSuccess;
}

Configuration

The tool follows the zero config principle - this means you can run it without having to configure anything. However, there are a bunch of configuration options if you need to adjust the tool. For simplicity, you can just define them in your pubspec.yaml below the dart_pre_commit key, but it is also possible to move it to a separate file.

The configuration uses the following pattern:

dart_pre_commit:
  task_1: null
  task_2: false
  task_3: true
  task_4:
    option1: true
    option2: info
    option3:
      - a
      - b
    ...
  ...

Each task corresponds to the configuration of the task with that name. The values can be:

  • null (or missing): The default configuration of the task is used
  • true or false: Explicitly enable or disable the task. If enabled, still uses the default configuration.
  • <config-map>: Explicitly enable the task and use the given configuration in addition to the default configuration. This allows you to overwrite either some or all of the configuration options of a task.

The default tasks and their options, if they have any, are defined follows. However, you can always create your own, custom tasks with customized configurations.

The tool also accepts some command line arguments. Run dart_pre_commit --help to get more information on them.

Format task

Task-ID: format
Configurable: Yes
Enabled: Always

This tasks checks all staged files for their formatting and corrects the formatting, if necessary. Internally, it uses the dart format command to accomplish this.

Options

Option Type Default Description
line-length int? null The line length the formatter should use. If unset, the recommended default for dart (currently 80) is used.

Test Imports Task

Task-ID: test-imports
Configurable: No
Enabled: Always

This task scans all test files to ensure they only import src libraries. For integration tests or in cases, where sources are purposefully not placed below src, you can ignore those imports as follows:

import 'package:my_app/src/src.dart';  // OK
import 'package:my_app/my_app.dart';  // NOT OK
// ignore: test_library_import
import 'package:my_app/my_app.dart';  // OK

Analyze Task

Task-ID: analyze
Configurable: Yes
Enabled: Always

This tasks checks all files for static analysis issues. Internally, this runs dart analyze to check for problems.

Options

Option Type Default Description
error-level enum info The severity level that should cause the task to reject the commit. See possible values below.

Values for error-level:

  • error: Only fatal errors are reported
  • warning: fatal errors and warnings are reported
  • info: fatal errors, warnings and linter issues are reported

Custom Lint Task

Task-ID: custom-lint
Configurable: No
Enabled: Only if custom_lint is installed as direct (dev) dependency

This tasks runs the custom_lint tool on your project to run additional, customized lints, if you have any. This can be very useful, especially for framework packages like riverpod, but also simpler ones like equatable.

Pro-Hint: You can use this customized pub.dev search query to find linter plugins for your packages: https://pub.dev/packages?q=dependency%3Acustom_lint_builder

Library Exports Task

Task-ID: lib-exports
Configurable: No
Enabled: Only if publish_to is not set to none

Scans all staged src files and checks if all files, that define at least one public top level element (internal or visibleFor* elements do not count), are exported publicly in at least one file directly below the lib directory.

Flutter Compatibility Task

Task-ID: flutter-compat
Configurable: No
Enabled: Only for pure dart projects

If changes have been made to the pubspec.yaml, this task will try to add this project as dependency to an empty, newly created flutter project to check if all version constraints of all dependencies are compatible with the latest flutter version.

Important: This task requires you to have flutter installed the the flutter binary to be available in your path. If this is not the case, you should explicitly disable the task.

Outdated Task

Task-ID: outdated
Configurable: Yes
Enabled: Always

Checks if any packages have available updates. This task always runs, even if no changes to the dependencies have been made. If any package has updates greater than the defined allowed level, the commit will fail.

Options

Option Type Default Description
level enum any The level of "outdated-ness" that is allowed. See possible values below.
allowed List<String> [] A list of packages that are allowed to be outdated, even if the level would otherwise reject them. Sometimes needed if updates break your code.

Values for level:

  • major: only check for major package updates
  • minor: check for major and minor updates
  • patch: check for major, minor and patch updates
  • any: check for all updates, except pre-releases

Pull Up Dependencies Task

Task-ID: pull-up-dependencies
Configurable: Yes
Enabled: Always

Checks if any dependencies in the pubspec.yaml have version constraints that allow lower versions than the ones resolved in the lockfile. If thats the case, the task will reject the commit. If the lockfile is ignored, this task always runs, otherwise it only runs if changes to the lockfile have been staged.

Options

Option Type Default Description
allowed List<String> [] A list of packages that are allowed to not be pulled up, even if their version constrains imply it. Can be useful to keep backwards compatibility.

OSV-Scanner Task

Task-ID: osv-scanner
Configurable: Yes
Enabled: Only if the osv-scanner binary is found in your PATH

When enabled, the pubspec.lock file is analyzed by the OSV-Scanner for known vulnerabilities in dependent packages. The task will fail in case such dependencies are found.

Options

Option Type Default Description
lockfile-only bool true If set to true, then only the pubspec.lock itself is scanned. Otherwise, the whole source folder is scanned.
config String? null If specified, the config file path will be passed to the osv scanner. Otherwise, the scanner tries to auto-detect the configuration.

Documentation

The documentation is available at https://pub.dev/documentation/dart_pre_commit/latest/. A full example can be found at https://pub.dev/packages/dart_pre_commit/example.

dart_pre_commit's People

Contributors

f22hd avatar felix-barz-brickmakers avatar skycoder42 avatar taosif7-dreamorbit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

dart_pre_commit's Issues

`error-level` option does not work under `analyze` task

I am looking to specify error-level as error, implying that the analyzer should only fail in the event that an "error" is found. However, it is still failing to commit when a "warning" is found.

Here is my configuration in the pubspec.yaml file:

dart_pre_commit:
  analyze:
    error-level: error
    scan-mode: staged

I then made a change to a file that does have a linter warning present (the variable _authHandler is not used), and tried to commit.

Because this is just a warning, and not an error, I should be allowed to commit.
After running git commit -m "dummy commit", this is what my terminal shows:

git commit -m "dummy commit"
โœ… Accepted file lib/services/org_chart_service.dart
๐Ÿ”Ž Running analyze...
      warning - lib/services/org_chart_service.dart:13:21 - The value of the field '_authHandler' isn't used. Try removing the field, or using it. - unused_field
    1 issue(s) found.
โŒ Completed analyze, found problems

So, it seems to me that the error-level config is not actually respecting the value that is being passed in, and always falls back on the default of "info".

Document custom-lint task

I can't find any documentation for the new custom-lint task.

It was causing the commit hook to fail with this error in my project:

Could not find package `custom_lint` or file `custom_lint`
  [EXC] "dart run custom_lint" failed with exit code 255
#0      ProgramRunner.stream (package:dart_pre_commit/src/util/program_runner.dart:91:11)
<asynchronous suspension>

I've managed to turn it off by adding

dart_pre_commit:
  custom-lint: false

to pubspec.yaml.

pre-commit fails in VSCode due to `version solving failed` in flutter project

When committing via the VSCode built in source control panel in my flutter project, the pre-commit fails with following error:

Because emotely depends on flutter_driver from sdk which doesn't exist (the Flutter SDK is not available), version solving failed.

Flutter users should run `flutter pub get` instead of `dart pub get`.

I was able to fix this by replacing the content of the git pre-commit file

#!/bin/sh
exec dart run dart_pre_commit # specify custom options here

with

#!/bin/sh
exec flutter pub run dart_pre_commit # specify custom options here

This might be worth adding to the documentation.

Add support for fvm

Since this plugin directly runs the flutter ... and dart ... commands, the projects that are using different version of flutter via fvm, fail to use the fvm flutter version for analysis. We need to add support for fvm in flutter and dart analyse commands.

A proposed solution is that we can detect .fvm folder in the project root and add "fvm" prefix before all flutter and dart commands in execution.

Export all sub-steps

  • All steps (fixImports, format, analyze, pullUpDependencies) should be exported
  • They should not directly depend on git anymore, so they can be invoked as pleased

Add outdated packages check

  • should use pub outdated to check for upgradable/resolvable packages
  • should also support the nullsafe check

dart_test_tools-4.7.0/.../test_import_linter.dart: Error: A value of type 'String?' can't be assigned to a variable of type 'DirectiveUri?'.

I'm getting an error trying to run this:

% dart run dart_pre_commit
Building package executable... 
Failed to build dart_pre_commit:dart_pre_commit:
../../.pub-cache/hosted/pub.dartlang.org/dart_test_tools-4.7.0/lib/src/lint/test_import_linter.dart:105:32: Error: A value of type 'String?' can't be assigned to a variable of type 'DirectiveUri?'.
 - 'DirectiveUri' is from 'package:analyzer/dart/element/element.dart' ('../../.pub-cache/hosted/pub.dartlang.org/analyzer-4.7.0/lib/dart/element/element.dart').
      directiveUri = directive.element?.uri;
                               ^
../../.pub-cache/hosted/pub.dartlang.org/dart_test_tools-4.7.0/lib/src/lint/test_import_linter.dart:107:32: Error: A value of type 'String?' can't be assigned to a variable of type 'DirectiveUri?'.
 - 'DirectiveUri' is from 'package:analyzer/dart/element/element.dart' ('../../.pub-cache/hosted/pub.dartlang.org/analyzer-4.7.0/lib/dart/element/element.dart').
      directiveUri = directive.element?.uri;
                               ^
Failed to build dart_pre_commit:dart_pre_commit:
../../.pub-cache/hosted/pub.dartlang.org/dart_test_tools-4.7.0/lib/src/lint/test_import_linter.dart:105:32: Error: A value of type 'String?' can't be assigned to a variable of type 'DirectiveUri?'.
 - 'DirectiveUri' is from 'package:analyzer/dart/element/element.dart' ('../../.pub-cache/hosted/pub.dartlang.org/analyzer-4.7.0/lib/dart/element/element.dart').
      directiveUri = directive.element?.uri;
                               ^
../../.pub-cache/hosted/pub.dartlang.org/dart_test_tools-4.7.0/lib/src/lint/test_import_linter.dart:107:32: Error: A value of type 'String?' can't be assigned to a variable of type 'DirectiveUri?'.
 - 'DirectiveUri' is from 'package:analyzer/dart/element/element.dart' ('../../.pub-cache/hosted/pub.dartlang.org/analyzer-4.7.0/lib/dart/element/element.dart').
      directiveUri = directive.element?.uri;

It was working so I'm guessing its related to flutter and other package upgrades in my project.

Pull up crashes when pubspec.yaml contains dependency not found in lockfile

$ git commit -m "use final fields instead of getters"
> Running pre_commit hook...
Scanning lib/src/firebase_account.dart...
Scanning lib/src/firebase_auth.dart...
Scanning lib/src/profile_update.dart...
Scanning lib/src/rest_api.dart...
Running dart analyze...
0 issue(s) found.
Checking for updates packages...
Unhandled exception:
NoSuchMethodError: The method '>' was called on null.
Receiver: null
Tried calling: >(Instance of 'Version')
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1      PullUpDependencies._pullUpVersions (package:dart_pre_commit/src/pull_up_dependencies.dart:99:31)
#2      PullUpDependencies.call (package:dart_pre_commit/src/pull_up_dependencies.dart:32:21)
<asynchronous suspension>
#3      Hooks.call (package:dart_pre_commit/src/hooks.dart:224:38)
<asynchronous suspension>
#4      main (file:///C:/Users/felix.barz/repos/other/firebase_rest_auth/tool/pre_commit.dart:9:29)
<asynchronous suspension>
#5      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:299:32)
#6      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

> Error detected in pre_commit hook.

run specific tasks on staged files only

I would like to run the analyze and custom-lint tasks only on staged files, is there a way I could achieve that?
I have some changes on some files that I'm not trying to commit yet, therefore, I'm not able to commit one file that would actually pass the dart analyzer.

the tool should not prevent me from committing a file because other unstaged files have some issues with the analyze task.

thank you.

Support import comments

The following samples should work:

// this is a comment
import '...';

import '...'; // this is also comment

lib-exports on flutter skeleton app

A flutter skeleton creates public MyApp class that consumes a public SampleItemListView class. These aren't exported and I'm not sure it makes sense to do so for a flutter app.

Should the lib-exports script handle this different? Should I start exporting my Flutter app components? Should I just not use the lib-exports script for flutter? Perhaps documenting this is the solution.

dart run dart_pre_commit -l debug
...
๐Ÿ”Ž Running lib-exports...
    lib/src/app.dart:10:1 - File has package-public declaration
    lib/src/sample_feature/sample_item_list_view.dart:7:1 - File has package-public declaration
    lib/src/app.dart - Source file is not exported anywhere
    lib/src/sample_feature/sample_item_list_view.dart - Source file is not exported anywhere
โŒ Completed lib-exports, found problems

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.