Skip to content

Commit e483e6d

Browse files
davidmorganlrhn
authored andcommitted
[macros] Add dart_model exploration code. (#3851)
1 parent e751169 commit e483e6d

32 files changed

+1573
-0
lines changed

working/macros/dart_model/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# `dart_model` exploration
2+
3+
Code exploring
4+
[a query-like API](https://github.com/dart-lang/language/issues/3706) for
5+
macros, in particular with regard to incremental build performance and
6+
convenience for macro authors.
7+
8+
_This code will be deleted/archived, do not use it for anything!_
9+
10+
Packages:
11+
12+
`dart_model` is a standalone data model, the input to a macro;\
13+
`dart_model_analyzer_service` serves `dart_model` queries using the analyzer
14+
as a library;\
15+
`dart_model_repl` is a REPL that can issue queries and watch code for changes\
16+
`macro_client` is for writing "macros";\
17+
`macro_host` hosts a set of "macros" running against a codebase;\
18+
`macro_protocol` is how "macros" communicate with their host;\
19+
`testing` is for test "macros" and experiments with them.
20+
21+
The "macros" referred to in this exploration are independent of the in-progress
22+
macro implementation, hence the "scare quotes".
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
linter:
2+
rules:
3+
# From package:lints/core.yaml
4+
- avoid_empty_else
5+
- avoid_relative_lib_imports
6+
- avoid_shadowing_type_parameters
7+
- avoid_types_as_parameter_names
8+
- await_only_futures
9+
- camel_case_extensions
10+
- camel_case_types
11+
- collection_methods_unrelated_type
12+
- curly_braces_in_flow_control_structures
13+
- dangling_library_doc_comments
14+
- depend_on_referenced_packages
15+
- empty_catches
16+
- file_names
17+
- hash_and_equals
18+
- implicit_call_tearoffs
19+
- library_annotations
20+
- no_duplicate_case_values
21+
- no_wildcard_variable_uses
22+
- non_constant_identifier_names
23+
- null_check_on_nullable_type_parameter
24+
- prefer_generic_function_type_aliases
25+
- prefer_is_empty
26+
- prefer_is_not_empty
27+
- prefer_iterable_whereType
28+
- prefer_typing_uninitialized_variables
29+
- provide_deprecation_message
30+
- secure_pubspec_urls
31+
- type_literal_in_constant_pattern
32+
- unnecessary_overrides
33+
- unrelated_type_equality_checks
34+
- use_string_in_part_of_directives
35+
- valid_regexps
36+
- void_checks
37+
# From package:lints/recommended.yaml
38+
- annotate_overrides
39+
- avoid_function_literals_in_foreach_calls
40+
- avoid_init_to_null
41+
- avoid_null_checks_in_equality_operators
42+
- avoid_renaming_method_parameters
43+
- avoid_return_types_on_setters
44+
- avoid_returning_null_for_void
45+
- avoid_single_cascade_in_expression_statements
46+
- constant_identifier_names
47+
- control_flow_in_finally
48+
- empty_constructor_bodies
49+
- empty_statements
50+
- exhaustive_cases
51+
- implementation_imports
52+
- library_prefixes
53+
- library_private_types_in_public_api
54+
- no_leading_underscores_for_library_prefixes
55+
- no_leading_underscores_for_local_identifiers
56+
- null_closures
57+
- overridden_fields
58+
- package_names
59+
- prefer_adjacent_string_concatenation
60+
- prefer_collection_literals
61+
- prefer_conditional_assignment
62+
- prefer_contains
63+
- prefer_final_fields
64+
- prefer_for_elements_to_map_fromIterable
65+
- prefer_function_declarations_over_variables
66+
- prefer_if_null_operators
67+
- prefer_initializing_formals
68+
- prefer_inlined_adds
69+
- prefer_interpolation_to_compose_strings
70+
- prefer_is_not_operator
71+
- prefer_null_aware_operators
72+
- prefer_spread_collections
73+
- recursive_getters
74+
- slash_for_doc_comments
75+
- type_init_formals
76+
- unnecessary_brace_in_string_interps
77+
- unnecessary_const
78+
- unnecessary_constructor_name
79+
- unnecessary_getters_setters
80+
- unnecessary_late
81+
- unnecessary_new
82+
- unnecessary_null_aware_assignments
83+
- unnecessary_null_in_if_null_operators
84+
- unnecessary_nullable_for_final_variable_declarations
85+
- unnecessary_string_escapes
86+
- unnecessary_string_interpolations
87+
- unnecessary_this
88+
- unnecessary_to_list_in_spreads
89+
- use_function_type_syntax_for_parameters
90+
- use_rethrow_when_possible
91+
- use_super_parameters
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
7+
import 'package:collection/collection.dart';
8+
9+
import 'model.dart';
10+
11+
extension type Update.fromJson(List<Object?> node) {
12+
Update({
13+
required Path path,
14+
required Object? value,
15+
}) : this.fromJson([path, value]);
16+
17+
Path get path => node[0] as Path;
18+
Object? get value => node[1];
19+
}
20+
21+
extension type Removal.fromJson(List<Object?> node) {
22+
Removal({
23+
required Path path,
24+
}) : this.fromJson(path.path);
25+
26+
Path get path => node as Path;
27+
}
28+
29+
extension type Delta.fromJson(Map<String, Object?> node) {
30+
Delta({
31+
List<Update>? updates,
32+
List<Removal>? removals,
33+
}) : this.fromJson({
34+
'updates': updates ?? [],
35+
'removals': removals ?? [],
36+
});
37+
38+
static Delta compute(Model previous, Model current) {
39+
final updates = <Update>[];
40+
final removals = <Removal>[];
41+
_compute(previous, current, Path([]), updates, removals);
42+
return Delta(updates: updates, removals: removals);
43+
}
44+
45+
static void _compute(Model previous, Model current, Path path,
46+
List<Update> updates, List<Removal> removals) {
47+
for (final key
48+
in previous.node.keys.followedBy(current.node.keys).toSet()) {
49+
final keyIsInPrevious = previous.node.containsKey(key);
50+
final keyIsInCurrent = current.node.containsKey(key);
51+
52+
if (keyIsInPrevious && !keyIsInCurrent) {
53+
removals.add(Removal(path: path.followedByOne(key)));
54+
} else if (keyIsInPrevious && keyIsInCurrent) {
55+
// It's either the same or a change.
56+
final previousValue = previous.node[key]!;
57+
final currentValue = current.node[key]!;
58+
59+
if (currentValue is Map<String, Object?>) {
60+
if (previousValue is Map<String, Object?>) {
61+
_compute(previousValue as Model, currentValue as Model,
62+
path.followedByOne(key), updates, removals);
63+
} else {
64+
updates.add(
65+
Update(path: path.followedByOne(key), value: currentValue));
66+
}
67+
} else if (currentValue is String) {
68+
if (previousValue is! String || previousValue != currentValue) {
69+
updates.add(
70+
Update(path: path.followedByOne(key), value: currentValue));
71+
}
72+
} else if (currentValue is List) {
73+
if (previousValue is! List ||
74+
!const DeepCollectionEquality()
75+
.equals(previousValue, currentValue)) {
76+
updates.add(
77+
Update(path: path.followedByOne(key), value: currentValue));
78+
}
79+
} else {
80+
throw 'Not sure what to do: $previousValue $currentValue';
81+
}
82+
} else {
83+
// It's new.
84+
updates.add(
85+
Update(path: path.followedByOne(key), value: current.node[key]));
86+
}
87+
}
88+
}
89+
90+
bool get isEmpty => updates.isEmpty && removals.isEmpty;
91+
92+
List<Update> get updates => (node['updates'] as List).cast();
93+
94+
List<Removal> get removals => (node['removals'] as List).cast();
95+
96+
String prettyPrint() => const JsonEncoder.withIndent(' ').convert(node);
97+
98+
void update(Model previous) {
99+
for (final update in updates) {
100+
previous.updateAtPath(update.path, update.value);
101+
}
102+
for (final removal in removals) {
103+
previous.removeAtPath(removal.path);
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)