# custom_lint 0.1.1

Lint rules are a powerful way to improve the maintainability of a project. Custom Lint allows package authors and developers to easily write custom lint rules.




## Installing
```
export PUB_HOSTED_URL=https://pub.mirror.p-hatan.ir
flutter pub add 'custom_lint:0.1.1'
```
Published: 2023-01-09T15:10:42.545626Z
Expected archive SHA-256: ee61018ed2c4bafa94458aca259d58335bfe6110f7ba244ad15a853b0513898b

Available in cache; verification: upstream_checksum
## Direct dependencies
- analyzer: ^5.0.0
- analyzer_plugin: ^0.11.0
- args: ^2.3.1
- async: ^2.8.2
- cli_util: ^0.3.5
- collection: ^1.15.0
- freezed_annotation: ^2.2.0
- json_annotation: ^4.7.0
- json_serializable: ^6.5.4
- meta: ^1.7.0
- package_config: ^2.0.2
- path: ^1.8.0
- pub_semver: ^2.1.1
- pubspec_parse: ^1.2.0
- recase: ^4.0.0
- riverpod: ^2.0.0
- rxdart: ^0.27.7
- uuid: ^3.0.6
- yaml: ^3.1.0

## Package README (untrusted upstream documentation)
<p align="center">
  <h1>custom_lint</h1>
  <span>Tools for building custom lint rules.</span>
</p>

<p align="center">
  <a href="https://github.com/invertase/dart_custom_lint/blob/main/LICENSE">License</a>
</p>

---

 <a href="https://invertase.link/discord">
   <img src="https://img.shields.io/discord/295953187817521152.svg?style=flat-square&colorA=7289da&label=Chat%20on%20Discord" alt="Chat on Discord">
 </a>
 
---

## Index

- [Index](#index)
- [Tutorial](#tutorial)
- [About](#about)
- [Usage](#usage)
  - [Creating a custom lint package](#creating-a-custom-lint-package)
  - [Using our custom lint package in an application](#using-our-custom-lint-package-in-an-application)
  - [Obtaining the list of lints in the CI](#obtaining-the-list-of-lints-in-the-ci)
  - [Using the Dart debugger](#using-the-dart-debugger)
  - [Testing your plugins using expect\_lint](#testing-your-plugins-using-expect_lint)
- [FAQs](#faqs)
  - [Q. How do I get all the classes present in my source code?](#q-how-do-i-get-all-the-classes-present-in-my-source-code)
  - [Q. How do I get all the global variables present in my source code?](#q-how-do-i-get-all-the-global-variables-present-in-my-source-code)
  - [Q. How do I get the path to my currently opened dart file?](#q-how-do-i-get-the-path-to-my-currently-opened-dart-file)
  - [Q. I want to insert code at the end of the file. How can I get the required offset?](#q-i-want-to-insert-code-at-the-end-of-the-file-how-can-i-get-the-required-offset)

## Tutorial

You can read the latest [blog post](https://invertase.link/b18R) or watch the [advanced use case with custom_lint video](https://invertase.link/RNoz).

## About

Lint rules are a powerful way to improve the mainta∏inability of a project.
The more, the merrier!  
But while Dart offers a wide variety of lint rules by default, it cannot
reasonably include every possible lint. For example, Dart does not
include lints related to third-party packages.

Custom_lint fixes that by allowing package authors to write custom lint rules.

Custom_lint is similar to [analyzer_plugin], but goes deeper by trying to
provide a better developer experience.

That includes:

- A command-line to obtain the list of lints in your CI
  without having to write a command line yourself.
- A simplified project setup:  
  No need to deal with the `analyzer` server or error handling. Custom_lint
  takes care of that for you, so that you can focus on writing lints.
- Debugger support.  
  Inspect your lints using the Dart debugger and place breakpoints.
- Supports hot-reload/hot-restart:  
  Updating the source code of a linter plugin will dynamically restart it,
  without having to restart your IDE/analyzer server.
- Built-in support for `// ignore:` and `// ignore_for_file:`.
- Built-in testing mechanism using `// expect_lint`. See [Testing your plugins using expect_lint](#testing-your-plugins-using-expect_lint)
- Support for `print(...)` and exceptions:  
  If your plugin somehow throws or print debug messages, custom_lint
  will generate a log file with the messages/errors.

## Usage

Using custom_lint is split in two parts:

- how to define a custom_lint package
- how users can install our package in their application to see our newly defined lints

### Creating a custom lint package

To create a custom lint, you will need two things:

- updating your `pubspec.yaml` to include `custom_lint_builder` as a dependency:

  ```yaml
  # pubspec.yaml
  name: my_custom_lint_package
  environment:
    sdk: ">=2.16.0 <3.0.0"

  dependencies:
    # we will use analyzer for inspecting Dart files
    analyzer:
    analyzer_plugin:
    # custom_lint_builder will give us tools for writing lints
    custom_lint_builder:
  ```

- create a `lib/<my_pachage_name>.dart` file in your project with the following:

  ```dart
  // This is the entrypoint of our custom linter
  PluginBase createPlugin() => _ExampleLinter();

  // This class is the one that will analyze Dart files and return lints
  class _ExampleLinter extends PluginBase {
    @override
    Stream<Lint> getLints(ResolvedUnitResult resolvedUnitResult) async* {
      // A basic lint that shows at the top of the file.
      yield Lint(
        code: 'my_custom_lint_code',
        message: 'This is the description of our custom lint',
        // Where your lint will appear within the Dart file.
        // The following code will make appear at the top of the file (offset 0),
        // and be 10 characters long.
        location: resolvedUnitResult.lintLocationFromOffset(0, length: 10),
      );
    }
  }
  ```

That's it for defining a custom lint package!

Let's now use it in an application.

### Using our custom lint package in an application

For users to run custom_lint packages, there are a few steps:

- The application must contain an `analysis_options.yaml` with the following:

  ```yaml
  analyzer:
    plugins:
      - custom_lint
  ```

- The application also needs to add `custom_lint` and our package(s) as dev
  dependency in their application:

  ```yaml
  # The pubspec.yaml of an application using our lints
  name: example_app
  environment:
    sdk: ">=2.16.0 <3.0.0"

  dev_dependencies:
    custom_lint:
    my_custom_lint_package:
  ```

That's all!  
After running `pub get` (and possibly restarting their IDE), users should now
see our custom lints in their Dart files:

![screenshot of our custom lints in the IDE](https://raw.githubusercontent.com/invertase/dart_custom_lint/main/resources/lint_showcase.png)

### Obtaining the list of lints in the CI

Unfortunately, running `dart analyze` does not pick up our newly defined lints.  
We need a separate command for this.

To do that, users of our custom lint package can run the following inside their terminal:

```sh
$ dart run custom_lint
  lib/main.dart:0:0 • This is the description of our custom lint • my_custom_lint_code
```

If you are working on a Flutter project, run `flutter pub run custom_lint` instead.

### Using the Dart debugger

To debug plugins in custom_lint, you need to connect to plugins using "attach"
mode in your IDE (`cmd+shift+p` + `Debug: attach to Dart process` in VSCode).

When using this command, you will need a VM service URI provided by custom_lint.

There are two possible ways to obtain one:

- if you started your plugin using `custom_lint --watch`, it should be visible
  in the console output.
- if your plugin is started by your IDE, you can open the `custom_lint.log` file
  that custom_lint created next to the `pubspec.yaml` of your analyzed projects.

In both cases, what you're looking for is logs similar to:

```
The Dart VM service is listening on http://127.0.0.1:60671/9DS43lRMY90=/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:60671/9DS43lRMY90=/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A60671%2F9DS43lRMY90%3D%2Fws
```

What you'll want is the first URI. In this example, that is `http://127.0.0.1:60671/9DS43lRMY90=/`.
You can then pass this to your IDE, which should now be able to attach to the
plugin.

### Testing your plugins using expect_lint

Custom_lint comes with an official testing mechanism for asserting that your
plugins correctly work.

Testing your plugins is straightforward: Simply write a file that should contain
lints from your plugin (such as the example folder). Then, using a syntax
similar to `// ignore`, write a `// expect_lint: code` in the line before
your lint:

```dart
// expect_lint: riverpod_final_provider
var provider = Provider(...);
```

When doing this, there are two possible cases:

- The line after the `expect_lint` correctly contains the expected lint.  
  In that case, the lint is ignored (similarly to if we used `// ignore`)
- The next line does **not** contain the lint.
  In that case, the `expect_lint` comment will have an error.

This allows testing your plugins by simply running `custom_lint` on your test/example folder.
Then, if any expected lint is missing, the command will fail. But if your plugin correctly
emits the lint, the command will succeed.

---

## FAQs

### Q. How do I get all the classes present in my source code?

There are two ways of doing that. You can either use the `Element Tree` or the `Abstract Syntax Tree (AST)` to find all the classes. Here's an example with each approach.

- **Element Tree Approach**

  ```dart
  class _CustomLint extends PluginBase {
    @override
    Stream<Lint> getLints(ResolvedUnitResult resolvedUnitResult) async* {
      final library = resolvedUnitResult.libraryElement;
      final classes = library.topLevelElements.whereType<ClassElement>();
      ....
      ....
    }
  }
  ```

  The variable `classes` is of type `List<ClassElement>` and will contain all the ClassElements.

- **AST Approach**

  ```dart
  class ClassDeclarationVisitor extends GeneralizingAstVisitor<void> {
    ClassDeclarationVisitor({
      required this.onClassDeclarationVisit,
    });

    void Function(ClassDeclaration node) onClassDeclarationVisit;

    @override
    void visitClassDeclaration(ClassDeclaration node) {
      onClassDeclarationVisit(node);
      super.visitClassDeclaration(node);
    }
  }

  class _CustomLint extends PluginBase {
    @override
    Stream<Lint> getLints(ResolvedUnitResult resolvedUnitResult) async* {
      final classDeclarations = <ClassDeclaration>[];
      resolvedUnitResult.unit.visitChildren(
        ClassDeclarationVisitor(
          onClassDeclarationVisit: classDeclarations.add,
        ),
      );
      ....
      ....
    }
  }
  ```

  This way, the variable `classDeclarations` will contain list of `ClassDeclaration`.

---

### Q. How do I get all the global variables present in my source code?

Same as [above](#q-how-do-i-get-all-the-classes-present-in-my-source-code). Just replace `ClassElement` with `VariableElement` in the Element Tree Approach or replace `ClassDeclaration` with `VariableDeclaration` in the AST approach.

---

### Q. How do I get the path to my currently opened dart file?

You can use the instance of `ResolvedUnitResult` to call the `path` getter.

```dart
class _CustomLints extends PluginBase {
  @override
  Stream<Lint> getLints(ResolvedUnitResult resolvedUnitResult) async* {
    final path = resolvedUnitResult.path; // Gives the path of the currently opened Dart file
    ...
    ...
  }
}
```

---

### Q. I want to insert code at the end of the file. How can I get the required offset?

To find the offset for the end of the file (or simply put, find the end line of a Dart file), you can use the instance of `ResolvedUnitResult` to call the `end` getter.

```dart
class _CustomLints extends PluginBase {
  @override
  Stream<Lint> getLints(ResolvedUnitResult resolvedUnitResult) async* {
    final endLineOffset = resolvedUnitResult.unit.end; // Gives the offset for the last line in a Dart file
  }
}
```

---

<p align="center">
  <a href="https://invertase.io/?utm_source=readme&utm_medium=footer&utm_campaign=dart_custom_lint">
    <img width="75px" src="https://static.invertase.io/assets/invertase/invertase-rounded-avatar.png">
  </a>
  <p align="center">
    Built and maintained by <a href="https://invertase.io/?utm_source=readme&utm_medium=footer&utm_campaign=dart_custom_lint">Invertase</a>.
  </p>
</p>

[analyzer_plugin]: https://pub.dev/packages/analyzer_plugin

## Changelog
## 0.1.1

- Fix an issue where plugins were hot-reloaded when the file analyzed changed.
- Optimized analysis such that `PluginBase.getLints()` is theorically not reinvoked
  unless the file analyzed changed.

## 0.1.0

- **Breaking**: The plugin entrypoint has moved.  
  Plugins no-longer should define a `/bin/custom_lint.dart` file.
  Instead they should define a `/lib/<my_package_name>.dart`

- **Breaking**: The plugin entrypoint is modified. Plugins no-longer
  define a "main", but instead define a `createPlugin` function:

  Before:

  ```dart
  // /bin/custom_lint.dart
  void main(List<String> args, SendPort sendPort) {
    startPlugin(sendPort, MyPlugin());
  }
  ```

  After:

  ```dart
  // /lib/<my_package_name.dart
  MyPlugin createPlugin() => MyPlugin();
  ```

- Add assist support.
  Inside your plugins, you can now override `handleGetAssists`:

  ```dart
  import 'package:analyzer_plugin/protocol/protocol_generated.dart'
    as analyzer_plugin;

  class MyPlugin extends PluginBase {
    // ...

    Future<analyzer_plugin.EditGetAssistsResult> handleGetAssists(
      ResolvedUnitResult resolvedUnitResult, {
      required int offset,
      required int length,
    }) async {
        // TODO return some assists for the given offset
    }
  }
  ```

## 0.0.16

Fix `expect_lint` not working if the file doesn't contain any lint.

## 0.0.15

- Custom_lint now has a built-in mechanism for testing lints.
  Simply write a file that should contain lints for your plugin.
  Then, using a syntax similar to `// ignore`, write a `// expect_lint: code`
  in the line before your lint:

  ```dart
  // expect_lint: riverpod_final_provider
  var provider = Provider(...);
  ```

  When doing this, there are two possible cases:

  - The line after the `expect_lint` correctly contains the expected lint.  
    In that case, the lint is ignored (similarly to if we used `// ignore`)
  - The next line does **not** contain the lint.
    In that case, the `expect_lint` comment will have an error.

  This allows testing your plugins by simply running `custom_lint` on your test/example folder.
  Then, if any expected lint is missing, the command will fail. But if your plugin correctly
  emits the lint, the command will succeed.

- Upgrade analyzer/analzer_plugin

## 0.0.14

- Fix custom_lint not working in the IDE

## 0.0.13

- Add debugger and hot-reload support (Thanks to @TimWhiting)
- Correctly respect `exclude` obtains from the analysis_options.yaml
- Fix `dart analyze` incorrectly failing due to showing the "plugin is starting" lint.

## 0.0.12

- Fix custom_lint plugins not working in release mode and when using git dependencies (thanks to @TimWhiting)
- Fix command line exit code not being set properly (thansk to @andrzejchm)

## 0.0.11

Fix custom_lint not showing in the IDE

## 0.0.10+1

Update docs

## 0.0.10

- Upgrade Riverpod to 2.0.0
- Fix deprecation errors with analyzer

## 0.0.9+1

Update description and readme

## 0.0.9

- Lint fixes can now be used when placing the cursor on the last character of a lint
- improve pub score

## 0.0.8

Allow lints to emit fixes

## 0.0.7

Fix a bug where the custom_lint command line may not list all lints

## 0.0.6

feat!: getLints now is expected to return a `Stream<Lint>` instead of `Iterable<Lint>`

fix: a bug where the lints shown by the IDE could get out of sync with the actual content of the file

## 0.0.5

Fixed error reporting if a custom_lint plugin throws but the exception comes
from a package instead of the plugin itself.

## 0.0.4

- Fixed a bug where the command line could show IDE-only meant for debugging

## 0.0.3

PluginBase.getLints now receive a `ResolvedUnitResult` instead of a `LibraryElement`.

## 0.0.2

- Compilation errors are now visible within the `pubspec.yaml` of applications
  that are using the plugin.

- Plugins that are currently loading are now highlighted inside the `pubspec.yaml`
  of applications that are using the plugin.

- If a plugin throws when trying to analyze a Dart file, the IDE will now
  show the exception at the top of the analyzed file.

- Compilation errors, exceptions and prints are now accessible within
  a log file (`custom_lint.log`) inside applications using the plugin.

## 0.0.1

Initial release







 
