Skip to content

Commit

Permalink
Implement individual linter rule pages (dart-lang#4999)
Browse files Browse the repository at this point in the history
This PR is meant to finalize work to make dart.dev a satisfactory
replacement for
[`dart-lang.github.io/linter/`](https://dart-lang.github.io/linter/)
while `dart-lang/linter` moves to the SDK and we work on consolidating
and improving documentation
(dart-lang#4498).

It does this by (staged links after colon (`:`)):

- Updating the information on dart.dev/lints for the latest state of the
linter:
[/lints](https://dart-dev--pr4999-feature-linter-indiv-zetj76b0.web.app/tools/linter-rules)
- Making dart.dev/lints an index page rather than including all
information:
[/lints](https://dart-dev--pr4999-feature-linter-indiv-zetj76b0.web.app/tools/linter-rules)
- Moving the long-form lint rule documentation to individual pages found
at `dart.dev/lints/<rule>`:
[/lints/avoid_dynamic_calls](https://dart-dev--pr4999-feature-linter-indiv-zetj76b0.web.app/lints/avoid_dynamic_calls)
- It does this by using a Jekyll page generator based on
`linter_rules.json`.
- Adding a textual reference pointing to the recently released all
linter rules page
(dart-lang@38973de)
found at:
[/lints/all](https://dart-dev--pr4999-feature-linter-indiv-zetj76b0.web.app/lints/all)
- Updating and links and references for these previous changes (mostly
Effective Dart).

Contributes to https://github.com/dart-lang/linter/issues/4460,
dart-lang#4498,
dart-lang#4499

**Note:** This is an intermediate step and doesn't aim to improve the
formatting or style of the information on the index or individual pages.
That will be part of follow-up work as we work on improvements to the
diagnostic linter rule documentation.
  • Loading branch information
parlough authored and rmacnak-google committed Sep 5, 2023
1 parent 35cebb7 commit a448ff1
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 93 deletions.
6 changes: 2 additions & 4 deletions firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,9 @@
{ "source": "/language/control-flow", "destination": "/language/loops", "type": 301 },
{ "source": "/language/enum", "destination": "/language/enums", "type": 301 },
{ "source": "/language/generators", "destination": "/language/functions#generators", "type": 301 },
{ "source": "/lint", "destination": "/tools/linter-rules", "type": 301 },
{ "source": "/lint/:lint*", "destination": "/tools/linter-rules#:lint", "type": 301 },
{ "source": "/linter/lints/:lint*", "destination": "/tools/linter-rules#:lint", "type": 301 },
{ "source": "/linter/lints/:lint*", "destination": "/tools/linter-rules/:lint", "type": 301 },
{ "source": "/lints", "destination": "/tools/linter-rules", "type": 301 },
{ "source": "/lints/:lint*", "destination": "/tools/linter-rules#:lint", "type": 301 },
{ "source": "/lints/:lint*", "destination": "/tools/linter-rules/:lint", "type": 301 },
{ "source": "/logos", "destination": "/brand", "type": 301 },
{ "source": "/mailing-list", "destination": "https://groups.google.com/a/dartlang.org/forum/#!forum/misc", "type": 301 },
{ "source": "/mobile", "destination": "/multiplatform-apps", "type": 301 },
Expand Down
14 changes: 7 additions & 7 deletions src/_data/linter_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@
"incompatible": [],
"sets": [],
"fixStatus": "needsFix",
"details": "Elements which are annotated with `@deprecated` should not be referenced from\nwithin the package in which they are declared.\n\n**AVOID** using deprecated elements.\n\n...\n\n**BAD:**\n```dart\n// Declared in one library:\nclass Foo {\n @Deprecated(\"Use 'm2' instead\")\n void m1() {}\n\n void m2({\n @Deprecated('This is an old parameter') int? p,\n })\n}\n\n@Deprecated('Do not use')\nint x = 0;\n\n// In the same or another library, but within the same package:\nvoid m(Foo foo) {\n foo.m1();\n foo.m2(p: 7);\n x = 1;\n}\n```\n\nDeprecated elements can be used from within _other_ deprecated elements, in\norder to allow for the deprecation of a collection of APIs together as one unit.\n\n**GOOD:**\n```dart\n// Declared in one library:\nclass Foo {\n @Deprecated(\"Use 'm2' instead\")\n void m1() {}\n\n void m2({\n @Deprecated('This is an old parameter') int? p,\n })\n}\n\n@Deprecated('Do not use')\nint x = 0;\n\n// In the same or another library, but within the same package:\n@Deprecated('Do not use')\nvoid m(Foo foo) {\n foo.m1();\n foo.m2(p: 7);\n x = 1;\n}\n```\n",
"details": "Elements that are annotated with `@Deprecated` should not be referenced from\nwithin the package in which they are declared.\n\n**AVOID** using deprecated elements.\n\n...\n\n**BAD:**\n```dart\n// Declared in one library:\nclass Foo {\n @Deprecated(\"Use 'm2' instead\")\n void m1() {}\n\n void m2({\n @Deprecated('This is an old parameter') int? p,\n })\n}\n\n@Deprecated('Do not use')\nint x = 0;\n\n// In the same or another library, but within the same package:\nvoid m(Foo foo) {\n foo.m1();\n foo.m2(p: 7);\n x = 1;\n}\n```\n\nDeprecated elements can be used from within _other_ deprecated elements, in\norder to allow for the deprecation of a collection of APIs together as one unit.\n\n**GOOD:**\n```dart\n// Declared in one library:\nclass Foo {\n @Deprecated(\"Use 'm2' instead\")\n void m1() {}\n\n void m2({\n @Deprecated('This is an old parameter') int? p,\n })\n}\n\n@Deprecated('Do not use')\nint x = 0;\n\n// In the same or another library, but within the same package:\n@Deprecated('Do not use')\nvoid m(Foo foo) {\n foo.m1();\n foo.m2(p: 7);\n x = 1;\n}\n```\n",
"sinceDartSdk": "3.0.0"
},
{
Expand Down Expand Up @@ -369,7 +369,7 @@
"sets": [],
"fixStatus": "needsEvaluation",
"details": "**DON'T** assign a variable to itself. Usually this is a mistake.\n\n**BAD:**\n```dart\nclass C {\n int x;\n\n C(int x) {\n x = x;\n }\n}\n```\n\n**GOOD:**\n```dart\nclass C {\n int x;\n\n C(int x) : x = x;\n}\n```\n\n**GOOD:**\n```dart\nclass C {\n int x;\n\n C(int x) {\n this.x = x;\n }\n}\n```\n\n**BAD:**\n```dart\nclass C {\n int _x = 5;\n\n int get x => _x;\n\n set x(int x) {\n _x = x;\n _customUpdateLogic();\n }\n\n void _customUpdateLogic() {\n print('updated');\n }\n\n void example() {\n x = x;\n }\n}\n```\n\n**GOOD:**\n```dart\nclass C {\n int _x = 5;\n\n int get x => _x;\n\n set x(int x) {\n _x = x;\n _customUpdateLogic();\n }\n\n void _customUpdateLogic() {\n print('updated');\n }\n\n void example() {\n _customUpdateLogic();\n }\n}\n```\n\n**BAD:**\n```dart\nclass C {\n int x = 5;\n\n void update(C other) {\n this.x = this.x;\n }\n}\n```\n\n**GOOD:**\n```dart\nclass C {\n int x = 5;\n\n void update(C other) {\n this.x = other.x;\n }\n}\n```\n\n",
"sinceDartSdk": "Unreleased"
"sinceDartSdk": "3.1.0-wip"
},
{
"name": "no_wildcard_variable_uses",
Expand All @@ -378,9 +378,9 @@
"state": "stable",
"incompatible": [],
"sets": [],
"fixStatus": "unregistered",
"fixStatus": "needsEvaluation",
"details": "**DON'T** use wildcard parameters or variables.\n\nWildcard parameters and local variables\n(e.g. underscore-only names like `_`, `__`, `___`, etc.) will\nbecome non-binding in a future version of the Dart language.\nAny existing code that uses wildcard parameters or variables will\nbreak. In anticipation of this change, and to make adoption easier,\nthis lint disallows wildcard and variable parameter uses.\n\n\n**BAD:**\n```dart\nvar _ = 1;\nprint(_); // LINT\n```\n\n```dart\nvoid f(int __) {\n print(__); // LINT multiple underscores too\n}\n```\n\n**GOOD:**\n```dart\nfor (var _ in [1, 2, 3]) count++;\n```\n\n```dart\nvar [a, _, b, _] = [1, 2, 3, 4];\n```\n",
"sinceDartSdk": "Unreleased"
"sinceDartSdk": "3.1.0-wip"
},
{
"name": "prefer_relative_imports",
Expand Down Expand Up @@ -453,7 +453,7 @@
"recommended",
"flutter"
],
"fixStatus": "noFix",
"fixStatus": "unregistered",
"details": "**DON'T** Compare references of unrelated types for equality.\n\nComparing references of a type where neither is a subtype of the other most\nlikely will return `false` and might not reflect programmer's intent.\n\n`Int64` and `Int32` from `package:fixnum` allow comparing to `int` provided\nthe `int` is on the right hand side. The lint allows this as a special case. \n\n**BAD:**\n```dart\nvoid someFunction() {\n var x = '1';\n if (x == 1) print('someFunction'); // LINT\n}\n```\n\n**BAD:**\n```dart\nvoid someFunction1() {\n String x = '1';\n if (x == 1) print('someFunction1'); // LINT\n}\n```\n\n**BAD:**\n```dart\nvoid someFunction13(DerivedClass2 instance) {\n var other = DerivedClass3();\n\n if (other == instance) print('someFunction13'); // LINT\n}\n\nclass ClassBase {}\n\nclass DerivedClass1 extends ClassBase {}\n\nabstract class Mixin {}\n\nclass DerivedClass2 extends ClassBase with Mixin {}\n\nclass DerivedClass3 extends ClassBase implements Mixin {}\n```\n\n**GOOD:**\n```dart\nvoid someFunction2() {\n var x = '1';\n var y = '2';\n if (x == y) print(someFunction2); // OK\n}\n```\n\n**GOOD:**\n```dart\nvoid someFunction3() {\n for (var i = 0; i < 10; i++) {\n if (i == 0) print(someFunction3); // OK\n }\n}\n```\n\n**GOOD:**\n```dart\nvoid someFunction4() {\n var x = '1';\n if (x == null) print(someFunction4); // OK\n}\n```\n\n**GOOD:**\n```dart\nvoid someFunction7() {\n List someList;\n\n if (someList.length == 0) print('someFunction7'); // OK\n}\n```\n\n**GOOD:**\n```dart\nvoid someFunction8(ClassBase instance) {\n DerivedClass1 other;\n\n if (other == instance) print('someFunction8'); // OK\n}\n```\n\n**GOOD:**\n```dart\nvoid someFunction10(unknown) {\n var what = unknown - 1;\n for (var index = 0; index < unknown; index++) {\n if (what == index) print('someFunction10'); // OK\n }\n}\n```\n\n**GOOD:**\n```dart\nvoid someFunction11(Mixin instance) {\n var other = DerivedClass2();\n\n if (other == instance) print('someFunction11'); // OK\n if (other != instance) print('!someFunction11'); // OK\n}\n\nclass ClassBase {}\n\nabstract class Mixin {}\n\nclass DerivedClass2 extends ClassBase with Mixin {}\n```\n\n",
"sinceDartSdk": "2.0.0"
},
Expand Down Expand Up @@ -1388,7 +1388,7 @@
"sets": [],
"fixStatus": "needsFix",
"details": "**DO** use super parameter names that match their corresponding super\nconstructor's parameter names.\n\n**BAD:**\n\n```dart\nclass Rectangle {\n final int width;\n final int height;\n \n Rectangle(this.width, this.height);\n}\n\nclass ColoredRectangle extends Rectangle {\n final Color color;\n \n ColoredRectangle(\n this.color,\n super.height, // Bad, actually corresponds to the `width` parameter.\n super.width, // Bad, actually corresponds to the `height` parameter.\n ); \n}\n```\n\n**GOOD:**\n\n```dart\nclass Rectangle {\n final int width;\n final int height;\n \n Rectangle(this.width, this.height);\n}\n\nclass ColoredRectangle extends Rectangle {\n final Color color;\n \n ColoredRectangle(\n this.color,\n super.width,\n super.height, \n ); \n}\n```\n",
"sinceDartSdk": "Unreleased"
"sinceDartSdk": "3.1.0-wip"
},
{
"name": "missing_whitespace_between_adjacent_strings",
Expand Down Expand Up @@ -1449,7 +1449,7 @@
"sets": [],
"fixStatus": "hasFix",
"details": "From [Effective Dart](https://dart.dev/effective-dart/usage#dont-use-true-or-false-in-equality-operations):\n\n**DON'T** use `true` or `false` in equality operations.\n\nThis lint applies only if the expression is of a non-nullable `bool` type.\n\n**BAD:**\n```dart\nif (someBool == true) {\n}\nwhile (someBool == false) {\n}\n```\n\n**GOOD:**\n```dart\nif (someBool) {\n}\nwhile (!someBool) {\n}\n```\n",
"sinceDartSdk": "Unreleased"
"sinceDartSdk": "3.1.0-wip"
},
{
"name": "no_runtimeType_toString",
Expand Down
80 changes: 80 additions & 0 deletions src/_includes/linter-page-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{% assign lint = include.lint -%}

{{lint.description}}

{% if lint.sinceDartSdk == "Unreleased" %}
_This rule is currently **experimental**
and not yet available in a stable SDK._
{% elsif lint.state == "removed" %}
_This rule has been removed as of the latest Dart releases._
{% elsif lint.state != "stable" %}
_This rule is currently **{{lint.state}}**
and available as of Dart {{lint.sinceDartSdk}}._
{% else %}
_This rule is available as of Dart {{lint.sinceDartSdk}}._
{% endif %}

{% if lint.sets != empty %}

{% assign rule_sets = "" %}

{% for set in lint.sets %}

{% if set == "core" or set == "recommended" %}
{% assign set_link = "lints" %}
{% elsif set == "flutter" %}
{% assign set_link = "flutter_lints" %}
{% else %}
{% assign set_link = set %}
{% endif %}

{%- capture rule_set -%}
[{{set}}](/tools/linter-rules#{{set_link}}){% if forloop.last == false %},{% endif %}
{% endcapture %}

{%- assign rule_sets = rule_sets | append: rule_set -%}

{% endfor %}

<em>Rule sets: {{ rule_sets }}</em>

{% endif %}

{% if lint.fixStatus == "hasFix" %}
<em>This rule has a [quick fix](/tools/linter-rules#quick-fixes) available.</em>
{% endif %}

{% if lint.incompatible != empty %}

{% assign incompatible_rules = "" %}

{% for incompatible in lint.incompatible %}

{%- capture incompatible_rule -%}
[{{incompatible}}](/tools/linter-rules/{{incompatible}}){% if forloop.last == false %},{% endif %}
{% endcapture %}

{% assign incompatible_rules = incompatible_rules | append: incompatible_rule %}

{% endfor %}

<em>Incompatible rules: {{ incompatible_rules }}</em>

{% endif %}

## Details

{{lint.details}}

## Usage

To enable the `{{lint.name}}` rule,
add `{{lint.name}}` under **linter > rules** in your
[`analysis_options.yaml`](/guides/language/analysis-options)
file:

```yaml
linter:
rules:
- {{lint.name}}
```
4 changes: 2 additions & 2 deletions src/_includes/linter-rule-mention.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{:.linter-rule}
{% if include.rule %}
Linter rule: [{{include.rule}}](/tools/linter-rules#{{include.rule}})
Linter rule: [{{include.rule}}](/tools/linter-rules/{{include.rule}})
{% else %}
Linter rules: [{{include.rule1}}](/tools/linter-rules#{{include.rule1}}), [{{include.rule2}}](/tools/linter-rules#{{include.rule2}})
Linter rules: [{{include.rule1}}](/tools/linter-rules/{{include.rule1}}), [{{include.rule2}}](/tools/linter-rules/{{include.rule2}})
{% endif %}
81 changes: 23 additions & 58 deletions src/_includes/linter-rules-section.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,51 @@

{% if lint.group == include.type %}

### {{lint.name}}

{{lint.description}}

{% if lint.sinceDartSdk == "Unreleased" %}
_This rule is currently **experimental**
and not yet available in a stable SDK._
{% elsif lint.state == "removed" %}
_This rule has been removed as of the latest Dart releases._
{% elsif lint.state != "stable" %}
_This rule is currently **{{lint.state}}**
and available as of Dart {{lint.sinceDartSdk}}._
{% else %}
_This rule is available as of Dart {{lint.sinceDartSdk}}._
{% endif %}
{% assign badges = "" %}

{% if lint.sets != empty %}

{% assign rule_sets = "" %}

{% for set in lint.sets %}

{% if set == "core" or set == "recommended" %}
{% assign set_link = "lints" %}
{% elsif set == "flutter" %}
{% assign set_link = "flutter_lints" %}
{% assign set_link = "lints" %}
{% elsif set == "flutter" %}
{% assign set_link = "flutter_lints" %}
{% else %}
{% assign set_link = set %}
{% assign set_link = set %}
{% endif %}

{%- capture rule_set -%}
[{{set}}](#{{set_link}}){% if forloop.last == false %},{% endif %}
<a href="/tools/linter-rules#{{set_link}}">
<img src="/assets/img/tools/linter/style-{{set}}.svg" alt="{{set}} rule set">
</a>
{% endcapture %}

{%- assign rule_sets = rule_sets | append: rule_set -%}
{%- assign badges = badges | append: rule_set -%}

{% endfor %}

<em>Rule sets: {{ rule_sets }}</em>

{% endif %}

{% if lint.fixStatus == "hasFix" %}
<em>This rule has a [quick fix](#quick-fixes) available.</em>
{% endif %}

{% if lint.incompatible != empty %}

{% assign incompatible_rules = "" %}

{% for incompatible in lint.incompatible %}

{%- capture incompatible_rule -%}
[{{incompatible}}](#{{incompatible}}){% if forloop.last == false %},{% endif %}
{%- capture has_fix -%}
<a href="/tools/linter-rules#quick-fixes">
<img src="/assets/img/tools/linter/has-fix.svg" alt="Has a quick fix">
</a>
{% endcapture %}

{% assign incompatible_rules = incompatible_rules | append: incompatible_rule %}

{% endfor %}

<em>Incompatible rules: {{ incompatible_rules }}</em>

{%- assign badges = badges | append: has_fix -%}
{% endif %}

#### Details

{{lint.details}}

#### Usage

To enable the `{{lint.name}}` rule,
add `{{lint.name}}` under **linter > rules** in your
[`analysis_options.yaml`](/guides/language/analysis-options)
file:

```yaml
linter:
rules:
- {{lint.name}}
```
<a id="{{lint.name}}"></a>
{% if lint.sinceDartSdk contains "wip" %}
[`{{lint.name}}`](/tools/linter-rules/{{lint.name}}) _(Unreleased)_
{% elsif lint.state != "stable" %}
[`{{lint.name}}`](/tools/linter-rules/{{lint.name}}) _({{lint.state | capitalize}})_
{% else %}
[`{{lint.name}}`](/tools/linter-rules/{{lint.name}})
{% endif -%}
{% if badges != empty %}<br>{{ badges }}{% endif -%}<br>{{lint.description}}

{% endif %}

Expand Down
8 changes: 6 additions & 2 deletions src/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
{% endif -%}
<div>
{% include shared/page-github-links.html %}
<h1>{{page.title}}</h1>
{% if page.underscore_breaker_titles -%}
<h1>{{page.title | underscore_breaker}}</h1>
{% else %}
<h1>{{page.title }}</h1>
{% endif -%}
</div>
{% include navigation-toc.html id='site-toc--inline' collapsible=true %}
{{ content | inject_anchors }}
Expand All @@ -25,6 +29,6 @@ <h1>{{page.title}}</h1>
</article>
</main>
{% include page-footer.html %}

</body>
</html>
6 changes: 6 additions & 0 deletions src/_layouts/linter-rule-standalone.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
layout: default
---

{% capture linter-rule %}{% include linter-page-content.md lint=page.lint %}{% endcapture %}
{{ linter-rule | markdownify }}
31 changes: 31 additions & 0 deletions src/_plugins/linter_page_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Jekyll
class DiagnosticPageGenerator < Jekyll::Generator
def generate(site)
linter_rules = site.data['linter_rules']

linter_rules.each { |lint|
site.pages << LinterPage.new(site, lint)
}
end
end

class LinterPage < Jekyll::PageWithoutAFile
def initialize(site, lint)
@site = site
@base = site.source
@dir = "/tools/linter-rules"
@basename = lint['name']
@ext = '.md'
@name = "#{lint['name']}.md"
@content = ""

@data = {
'title' => basename,
'layout' => 'linter-rule-standalone',
'lint' => lint,
'underscore_breaker_titles' => true
}

end
end
end
1 change: 1 addition & 0 deletions src/assets/img/tools/linter/has-fix.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/img/tools/linter/style-core.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/img/tools/linter/style-flutter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/img/tools/linter/style-recommended.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/effective-dart/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ To keep the preamble of your file tidy, we have a prescribed order that
directives should appear in. Each "section" should be separated by a blank line.

A single linter rule handles all the ordering guidelines:
[directives_ordering.](/tools/linter-rules#directives_ordering)
[directives_ordering.](/tools/linter-rules/directives_ordering)


### DO place `dart:` imports before other imports
Expand Down
2 changes: 1 addition & 1 deletion src/effective-dart/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ you don't care about the type, then use `toList()`.

### DO use `whereType()` to filter a collection by type

{% include linter-rule-mention.md rule="prefer_iterable_wheretype" %}
{% include linter-rule-mention.md rule="prefer_iterable_whereType" %}

Let's say you have a list containing a mixture of objects, and you want to get
just the integers out of it. You could use `where()` like this:
Expand Down
Loading

0 comments on commit a448ff1

Please sign in to comment.