Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create jaspr_lints package #133

Merged
merged 23 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ Rather it embraces these differences to give the best of both worlds.
- **/examples**: Well-maintained and documented examples
- **/experiments**: Experimental apps or features, that are not part of the core framework (yet?) (may be broken).
- **/packages**:
- [**/jaspr**](https://github.com/schultek/jaspr/tree/main/packages/jaspr): The core framework package.
- **/jaspr**: The core framework package.
- **/jaspr_builder**: Code-generation builders for jaspr.
- **/jaspr_cli**: The command line interface of jaspr.
- **/jaspr_flutter_embed**: Flutter element embedding bindings for jaspr.
- **/jaspr_lints**: A collection of lints and assists for jaspr projects.
- **/jaspr_riverpod**: Riverpod implementation for jaspr.
- **/jaspr_router**: A router implementation for jaspr.
- **/jaspr_test**: A testing package for jaspr.
Expand Down
43 changes: 22 additions & 21 deletions apps/fluttercon/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,28 @@ class App extends AsyncStatelessComponent {
final sessions = (sessionsJson as List).map((s) => SessionMapper.fromMap(s)).toList();

yield Router(
redirect: (context, state) {
if (state.location == '/') return '/day-1';
return null;
},
routes: [
for (var i = 1; i < 4; i++)
Route(
path: '/day-$i',
title: 'Fluttercon Berlin 2024',
builder: (context, state) => SchedulePage(
day: i,
sessions: sessions.where((s) => s.startsAt.day == i + 2).toList(),
),
redirect: (context, state) {
if (state.location == '/') return '/day-1';
return null;
},
routes: [
for (var i = 1; i < 4; i++)
Route(
path: '/day-$i',
title: 'Fluttercon Berlin 2024',
builder: (context, state) => SchedulePage(
day: i,
sessions: sessions.where((s) => s.startsAt.day == i + 2).toList(),
),
Route(path: '/favorites', title: 'Favorites', builder: (context, state) => FavoritesPage()),
for (var session in sessions)
Route(
path: '/${session.slug}',
title: session.title,
builder: (context, state) => SessionPage(session: session),
),
]);
),
Route(path: '/favorites', title: 'Favorites', builder: (context, state) => FavoritesPage()),
for (var session in sessions)
Route(
path: '/${session.slug}',
title: session.title,
builder: (context, state) => SessionPage(session: session),
),
],
);
}
}
2 changes: 0 additions & 2 deletions apps/fluttercon/lib/components/session_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,5 @@ class SessionCard extends StatelessComponent {
css('div').flexbox(),
css('button').box(position: Position.absolute(right: 10.px, top: 10.px)),
]),
...LikeButton.styles,
...Tag.styles,
];
}
3 changes: 2 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
{"title": "\uD83D\uDD79 Jaspr CLI", "href": "/get_started/cli"},
{"title": "\uD83D\uDCDF Rendering Modes", "href": "/get_started/modes"},
{"title": "\uD83D\uDCA7 Hydration", "href": "/get_started/hydration"},
{"title": "\uD83D\uDCE6 Project Structure", "href": "/get_started/project_structure"}
{"title": "\uD83D\uDCE6 Project Structure", "href": "/get_started/project_structure"},
{"title": "\uD83E\uDDF9 Linting", "href": "/get_started/linting"}
]
},
{
Expand Down
207 changes: 207 additions & 0 deletions docs/get_started/linting.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
title: Linting
description: Jaspr comes with its own set of lint rules and code assists.
---

Jaspr has its own linting and analyzer package called `jaspr_lints`. This comes pre-configured
with every new project and enables the following set of lint rules and code assists:

## Lint Rules

<Card>
<Property name="prefer_html_methods" type="Fix available">
Prefer html methods like `div(...)` over `DomComponent(tag: 'div', ...)`.

**BAD:**
```dart
yield DomComponent(
tag: 'div',
children: [
DomComponent(
tag: 'p',
child: Text('Hello World'),
),
],
);
```

**GOOD:**
```dart
yield div([
p([text('Hello World')]),
]);
```
</Property>
</Card>

<Card>
<Property name="sort_children_properties_last" type="Fix available">
Sort children properties last in html component methods.

This improves readability and best represents actual html.

**BAD:**
```dart
yield div([
p([text('Hello World')], classes: 'text-red'),
], id: 'main');
```

**GOOD:**
```dart
yield div(id: 'main', [
p(classes: 'text-red', [text('Hello World')]),
]);
```
</Property>
</Card>

## Code Assists

<Card>
<Property name="Create StatelessComponent" type="Top level">
Creates a new `StatelessComponent` class.
</Property>
</Card>

<Card>
<Property name="Create StatefulComponent" type="Top level">
Creates a new `StatefulComponent` and respective `State` class.
</Property>
</Card>

<Card>
<Property name="Create InheritedComponent" type="Top level">
Creates a new `InheritedComponent` class.
</Property>
</Card>

<Card>
<Property name="Convert to StatefulComponent" type="Class level">
Converts a custom `StatelessComponent` into a `StatefulComponent` and respective `State` class.
</Property>
</Card>

<Card>
<Property name="Convert to AsyncStatelessComponent" type="Class level">
Converts a custom `StatelessComponent` into an `AsyncStatelessComponent`.
</Property>
</Card>

<Card>
<Property name="Remove this component" type="Component tree level">
Surgically removes the selected component from the component tree, making its
children the new direct children of its parent.
</Property>
</Card>

<Card>
<Property name="Wrap with html..." type="Component tree level">
Wraps the selected component with a new html component.

```dart
yield div([
p([ // [!code ++]
span([text('Hello World')]),
]), // [!code ++]
]);
```
</Property>
</Card>

<Card>
<Property name="Wrap with component..." type="Component tree level">
Wraps the selected component with a new component.

```dart
yield div([
MyComponent( // [!code ++]
child: span([text('Hello World')]),
), // [!code ++]
]);
```
</Property>
</Card>

<Card>
<Property name="Wrap with Builder" type="Component tree level">
Wraps the selected component with a `Builder`.

```dart
yield div([
Builder(builder: (context) sync* { // [!code ++]
yield span([text('Hello World')]);
}), // [!code ++]
]);
```
</Property>
</Card>

<Card>
<Property name="Extract component" type="Component tree level">
Extracts the selected component plus subtree into a new `StatelessComponent`.
</Property>
</Card>

<Card>
<Property name="Add styles" type="Component tree level">
Adds new css styles to the selected component.

This will either add a new class name:
```dart
yield div(
classes: '[...]' // [!code ++]
[],
);

/* ... */

@css // [!code ++]
static List<StyleRule> styles = [ // [!code ++]
css('.[...]').box(...), // [!code ++]
]; // [!code ++]
```

Or use an existing id or class name:
```dart
yield div(id: 'content', []);

/* ... */

@css // [!code ++]
static List<StyleRule> styles = [ // [!code ++]
css('#content').box(...), // [!code ++]
]; // [!code ++]
```
</Property>
</Card>

<Card>
<Property name="Convert to web-only import" type="Directive level">
Converts any import to a web-only import using the [@Import](/utils/at_import) annotation.

```dart
import 'dart:html'; // [!code --]

@Import.onWeb('dart:html', show: [#window]) // [!code ++]
import 'filename.imports.dart'; // [!code ++]
```

This will auto-detect all used members from the import and add them to the `show` parameter.
</Property>
</Card>

<Card>
<Property name="Convert to server-only import">
Converts any import to a server-only import using the [@Import](/utils/at_import) annotation.

```dart
import 'dart:io'; // [!code --]

@Import.onServer('dart:io', show: [#HttpRequest]) // [!code ++]
import 'filename.imports.dart'; // [!code ++]
```

This will auto-detect all used members from the import and add them to the `show` parameter.
</Property>
</Card>
4 changes: 4 additions & 0 deletions docs/utils/at_import.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ This will:
- setup conditional imports for `dart:html` on the web
- write stubs on the server for all imported members.

<Success>
There is a code-assist available from `jaspr_lints` that converts your imports automatically!
See [Linting](/get_started/linting).
</Success>
---

You can extend this to multiple imports and mix web and server imports like this:
Expand Down
6 changes: 6 additions & 0 deletions packages/jaspr/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Unreleased minor
<<<<<<< feat/analyzer_plugin

- Include and setup `jaspr_lints` in newly created projects.
- Added `jaspr analyze` command to check all custom lints.
=======
- Added css variable for Unit, Color, Angle and FontFamily.
>>>>>>> main

## 0.15.0

Expand Down
6 changes: 6 additions & 0 deletions packages/jaspr/tool/generate_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ void main() {
var specFile = File('tool/data/html.json');
var specJson = jsonDecode(specFile.readAsStringSync()) as Map<String, dynamic>;

var allTags = <String>{};

for (var key in specJson.keys) {
var group = specJson[key] as Map<String, dynamic>;
var file = File('lib/src/components/html/$key.dart');
Expand All @@ -19,6 +21,7 @@ void main() {
continue;
}

allTags.add(tag);
content.write('\n${data['doc'].split('\n').map((t) => '/// $t\n').join()}');

var attrs = data['attributes'] as Map<String, dynamic>?;
Expand Down Expand Up @@ -206,4 +209,7 @@ void main() {

file.writeAsStringSync(content.toString());
}

var lintFile = File('../jaspr_lints/lib/src/all_html_tags.dart');
lintFile.writeAsStringSync('const allHtmlTags = {${allTags.map((t) => "'$t'").join(', ')}};\n');
}
2 changes: 2 additions & 0 deletions packages/jaspr_cli/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:cli_completion/cli_completion.dart';
import 'package:mason/mason.dart';
import 'package:pub_updater/pub_updater.dart';

import 'commands/analyze_command.dart';
import 'commands/base_command.dart';
import 'commands/build_command.dart';
import 'commands/clean_command.dart';
Expand Down Expand Up @@ -37,6 +38,7 @@ class JasprCommandRunner extends CompletionCommandRunner<CommandResult?> {
addCommand(CreateCommand());
addCommand(ServeCommand());
addCommand(BuildCommand());
addCommand(AnalyzeCommand());
addCommand(CleanCommand());
addCommand(UpdateCommand());
addCommand(DoctorCommand());
Expand Down
42 changes: 42 additions & 0 deletions packages/jaspr_cli/lib/src/commands/analyze_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'dart:io';

import '../logging.dart';
import 'base_command.dart';

class AnalyzeCommand extends BaseCommand {
AnalyzeCommand({super.logger}) {
argParser.addFlag(
'fatal-infos',
schultek marked this conversation as resolved.
Show resolved Hide resolved
help: 'Treat info level issues as fatal',
defaultsTo: true,
);
argParser.addFlag(
'fatal-warnings',
help: 'Treat warning level issues as fatal',
defaultsTo: true,
);
argParser.addFlag(
'fix',
help: 'Apply all possible fixes to the lint issues found.',
negatable: false,
);
}

@override
String get description => 'Report Jaspr specific lint warnings.';

@override
String get name => 'analyze';

@override
String get category => 'Tooling';

@override
Future<CommandResult?> run() async {
await super.run();

var process = await Process.start('dart', ['run', 'custom_lint', ...?argResults?.arguments]);

return CommandResult.running(watchProcess('custom_lint', process, tag: Tag.none), stop);
}
}
Loading
Loading