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

FED-194 Fully implement hooks exhaustive dependencies diagnostic, add tests #788

Merged
merged 206 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
206 commits
Select commit Hold shift + click to select a range
8f48ed2
Add test cases
greglittlefield-wf May 17, 2022
3b3ee9d
Add test cases and tool for porting them
greglittlefield-wf May 19, 2022
5654644
Handle a few more ccases
greglittlefield-wf May 19, 2022
2f1a8f9
Write output
greglittlefield-wf May 19, 2022
fc344aa
Handle adjacent commas in lists
greglittlefield-wf May 19, 2022
6ada50b
Wire up exhaustive deps tests
greglittlefield-wf May 19, 2022
13845df
Add missing dependency
greglittlefield-wf May 19, 2022
34b7ff6
Merge remote-tracking branch 'origin/analyzer-plugin-jam-base' into e…
greglittlefield-wf May 19, 2022
5410c1a
Enable exhaustive deps diagnostic
greglittlefield-wf May 19, 2022
d793eca
More test case fixes
greglittlefield-wf May 19, 2022
12ce453
Fix exhaustive deps not using correct ancestor function
greglittlefield-wf May 19, 2022
e210366
Tweak tests
greglittlefield-wf May 19, 2022
065f762
Fix expectation
greglittlefield-wf May 19, 2022
de1f6ea
Filter over_react_debug_analyzer_plugin_helper by default
greglittlefield-wf May 19, 2022
46ac338
Handle nested function components in porting logic
greglittlefield-wf May 19, 2022
343df5d
Disable noisy lint
greglittlefield-wf May 19, 2022
a97c5f1
Fix some more tests
greglittlefield-wf May 19, 2022
bdf5a3f
Add null safety migration hints
greglittlefield-wf May 19, 2022
f536046
Apply null safety migration to exhaustive deps diagnostic
greglittlefield-wf May 19, 2022
ef52f99
Fix WeakMap issues after null safety migration
greglittlefield-wf May 19, 2022
57be6ee
Fix simple warnings after migration
greglittlefield-wf May 19, 2022
0908423
Fix link
greglittlefield-wf May 19, 2022
6b1f3b6
Fix dead code involving null checks
greglittlefield-wf May 19, 2022
6720714
Clean up some minor non-null assertions
greglittlefield-wf May 19, 2022
86698d6
Consolidate some non-null assertions
greglittlefield-wf May 19, 2022
b40cc1e
Fix WeakSet nullability
greglittlefield-wf May 19, 2022
49c18f7
Fix memoization null safety
greglittlefield-wf May 19, 2022
fe93dc1
Clean up some non-null assertions and null checks
greglittlefield-wf May 19, 2022
5303705
Clean up more null checks and non-null assertions
greglittlefield-wf May 19, 2022
36db93f
Fix test setup
greglittlefield-wf May 19, 2022
baa2351
Fix unnecessary import
greglittlefield-wf May 19, 2022
ff8694a
Don't assume we're always inside a function component or hook
greglittlefield-wf May 19, 2022
9a045e5
Merge remote-tracking branch 'origin/analyzer-plugin-jam-base' into e…
greglittlefield-wf May 20, 2022
9103da3
Format
greglittlefield-wf May 20, 2022
0ee37dc
Manually complete port of some cases, remove some cases that can't oc…
greglittlefield-wf May 20, 2022
6c3d69c
Fix more test cases
greglittlefield-wf May 20, 2022
7a0b860
More fixes
greglittlefield-wf May 20, 2022
b65a23d
Fix isDeclaredInPureScope when not inside a function component or hook
greglittlefield-wf May 23, 2022
bf55e8b
Fix more cases
greglittlefield-wf May 23, 2022
ffba909
Fix most of the rest of the failures
greglittlefield-wf May 23, 2022
bf2f571
Remove hoisting case not valid in Dart
greglittlefield-wf May 23, 2022
b4e8bfa
Merge remote-tracking branch 'origin/master' into exhaustive-deps-tests
greglittlefield-wf Jul 21, 2022
657c5f4
Fix stable hook tearoff check
greglittlefield-wf Jul 21, 2022
5f74874
Remove test cases not applicable in Dart
greglittlefield-wf Jul 21, 2022
8b439f7
Format test cases
greglittlefield-wf Jul 22, 2022
9dd331e
Unindent test cases for diffing
greglittlefield-wf Jul 22, 2022
3e44753
Revert "Unindent test cases for diffing"
greglittlefield-wf Jul 22, 2022
f82f697
Prep test cases for diffing
greglittlefield-wf Jul 22, 2022
2c92f2a
Add trailing commas for better diffing
greglittlefield-wf Jul 22, 2022
af24a02
Bring over comments
greglittlefield-wf Jul 22, 2022
43018ff
Commit utilities for re-porting code with minimal diffs to allow for …
greglittlefield-wf Jul 22, 2022
fd9b7df
Update consuming test
greglittlefield-wf Jul 22, 2022
2f199c5
Remove JS test cases and code porting tooling
greglittlefield-wf Jul 22, 2022
9d3d321
Fix test cases, simplify error ignoring logic
greglittlefield-wf Jul 22, 2022
cc6e00a
Format
greglittlefield-wf Jul 22, 2022
7d8400c
Remove invalid test case
greglittlefield-wf Jul 22, 2022
e6e6f70
Fix logic around ref.current checks
greglittlefield-wf Jul 25, 2022
334c562
Clean up other places where we weren't handling PrefixedIdentifier
greglittlefield-wf Jul 25, 2022
02263d0
First stab at tests for expected error cases
greglittlefield-wf Jul 25, 2022
59611ed
Remove some invalid cases
greglittlefield-wf Jul 25, 2022
7ce1b89
Finish porting extra warning message content
greglittlefield-wf Jul 25, 2022
4e1efb5
Commit forgotten test base changes
greglittlefield-wf Jul 25, 2022
450a816
Clean up and finish porting logic for literal/constant dependencies
greglittlefield-wf Jul 25, 2022
5a90a0c
Fix up some test cases
greglittlefield-wf Jul 25, 2022
2c6c7f7
Replace usages of "array" in error messages with "list"
greglittlefield-wf Jul 25, 2022
515c0d4
Reinstate missing deps tests
greglittlefield-wf Jul 26, 2022
97e7863
Fix typo in message
greglittlefield-wf Jul 26, 2022
c9ba659
Fix some more test cases
greglittlefield-wf Jul 26, 2022
a47688e
Leave out preamble in test failures
greglittlefield-wf Jul 26, 2022
d41134f
Fix more test cases
greglittlefield-wf Jul 26, 2022
30d436d
Don't let useState's generic parameter get inferred as `Null`
greglittlefield-wf Jul 26, 2022
e2243df
Fix raw code being wrapped
greglittlefield-wf Jul 26, 2022
d0600d8
Parse and wire up additional_hooks configuration for exhaustive deps …
greglittlefield-wf Jul 26, 2022
e4ae616
Wire up test case configs
greglittlefield-wf Jul 27, 2022
2e5e665
Fix options not being picked up
greglittlefield-wf Jul 27, 2022
283014a
Add note about optionsFile being null unless set up immediately
greglittlefield-wf Jul 27, 2022
20dc478
Some progress on getNodeWithoutReactNamespace
greglittlefield-wf Jul 27, 2022
8de4af0
Progress toward fixing issues with how state.set is treated as a stab…
greglittlefield-wf Jul 29, 2022
418f5a8
Use for-in instead of forEach to enable better type promotion below
greglittlefield-wf Jul 29, 2022
a8975e0
Expect exact error messages to avoid false positives when containing …
greglittlefield-wf Jul 29, 2022
d588778
Update functional setState messages and expected messages to reflect …
greglittlefield-wf Jul 29, 2022
032e70f
Fix StateHook stable method tracking and recommendation logic
greglittlefield-wf Jul 29, 2022
f88944f
DRY up dependencies map code
greglittlefield-wf Jul 29, 2022
4b3cd26
Split out stable hook detection so it can be used in getDependency
greglittlefield-wf Jul 29, 2022
df1b38a
Fix message
greglittlefield-wf Jul 29, 2022
c1d3cf8
Ignore line numbers in messages in tests
greglittlefield-wf Jul 29, 2022
8ef8909
Fix cascade only running when dependency is not in map
greglittlefield-wf Jul 31, 2022
f71c718
Fix some test cases
greglittlefield-wf Aug 1, 2022
dc06722
Fix more test cases
greglittlefield-wf Aug 1, 2022
502ca53
Clean up unsupported node type try/catch logic
greglittlefield-wf Aug 1, 2022
7f00348
Fix new MethodInvocation handling in analyzePropertyChain messing wit…
greglittlefield-wf Aug 1, 2022
fbf2533
Fix expected error messages
greglittlefield-wf Aug 1, 2022
163b493
Fix local functions declared directly in component/hook not being pic…
greglittlefield-wf Aug 3, 2022
4f7e87e
Fix more test cases
greglittlefield-wf Aug 3, 2022
9b72b43
Fix more test cases
greglittlefield-wf Aug 3, 2022
a38a3e8
Fix isUsedOutsideOfHook logic
greglittlefield-wf Aug 4, 2022
9cc9d52
Fix test cases, update expected messages
greglittlefield-wf Aug 4, 2022
8f8a3ac
Fix more test cases and messages
greglittlefield-wf Aug 4, 2022
ddf4efe
Format
greglittlefield-wf Aug 4, 2022
116624e
Fix bad depType check using unused string, use constants
greglittlefield-wf Aug 4, 2022
c76f4f9
Add fixme
greglittlefield-wf Aug 4, 2022
9815c0c
Format
greglittlefield-wf Aug 4, 2022
7efbafc
Clean up scanForConstructions
greglittlefield-wf Aug 4, 2022
db3e2fc
Fix expected messages
greglittlefield-wf Aug 4, 2022
91814b4
Hack together special case error message for callable props
greglittlefield-wf Aug 4, 2022
9ba391c
Fix missing words in messages
greglittlefield-wf Aug 4, 2022
f36b60f
Fix test cases
greglittlefield-wf Aug 4, 2022
720cb3b
Fix test cases
greglittlefield-wf Aug 4, 2022
00aa7e2
Pull in other suites, fix test cases
greglittlefield-wf Aug 5, 2022
42d8009
More test case fixes
greglittlefield-wf Aug 5, 2022
3c1b61d
Fix more test cases
greglittlefield-wf Aug 8, 2022
9477019
Fix setStateWithUpdaterWithUpdater error message in edge case
greglittlefield-wf Aug 8, 2022
b5b2803
Make exhaustive hooks debugging use a comment flag
greglittlefield-wf Aug 8, 2022
27658f8
First stab at handling function props as their own dependencies
greglittlefield-wf Aug 8, 2022
434461a
Update test case
greglittlefield-wf Aug 8, 2022
3d217dd
Fix complex expressions not being detected
greglittlefield-wf Aug 8, 2022
6b4372d
Fix/remove test cases
greglittlefield-wf Aug 8, 2022
d8bd0cd
Move test case to valid set
greglittlefield-wf Aug 8, 2022
b00fbea
Clean up hook type checks
greglittlefield-wf Aug 9, 2022
08575d8
Fix reducer hook logic and test case
greglittlefield-wf Aug 9, 2022
9cb0101
Fix inline reducer suggestion case
greglittlefield-wf Aug 9, 2022
0a156ec
Properly detect inline usages of function props, improve message when…
greglittlefield-wf Aug 10, 2022
8b7d088
Fix most of the function prop cases
greglittlefield-wf Aug 11, 2022
00e6021
Refactor/fix callable prop error messages, remove unnecessary depende…
greglittlefield-wf Aug 11, 2022
8ffb5a4
Fix declared in callback check
greglittlefield-wf Aug 11, 2022
923af45
Update expected test case value
greglittlefield-wf Aug 12, 2022
befa42b
Remove not-applicable test case
greglittlefield-wf Aug 12, 2022
72addda
Address some fixmes/todos, cleanup
greglittlefield-wf Aug 12, 2022
4ab1a26
Clean up getDependency, usages of getSimpleTargetAndPropertyName
greglittlefield-wf Aug 12, 2022
99df585
Split out reusable utilities
greglittlefield-wf Aug 12, 2022
1edb238
Reorganize reflection-based constant instantiation code
greglittlefield-wf Aug 12, 2022
0b0136f
Split out and simplify prettyString implementation
greglittlefield-wf Aug 12, 2022
13c1f06
Refactor most logic into the diagnostic so that addErrorWithFix can b…
greglittlefield-wf Aug 15, 2022
9634764
Clean up `node` callback function body arg
greglittlefield-wf Aug 15, 2022
9f207e8
First stab at setting up tests for suggestions (fixes), fix test cases
greglittlefield-wf Aug 16, 2022
43284ac
Improve fix expectations, fix issues with multiple errors/fixes on th…
greglittlefield-wf Aug 16, 2022
c894645
Fix test cases
greglittlefield-wf Aug 16, 2022
b582e99
Fix test cases
greglittlefield-wf Aug 16, 2022
723df4a
Remove function wrapper by default since most cases don't need it
greglittlefield-wf Aug 16, 2022
0f203df
Fix typescript cases expected output
greglittlefield-wf Aug 16, 2022
74e8b7f
Remove old comments
greglittlefield-wf Aug 16, 2022
e736615
Add debugging statements for error mapping
greglittlefield-wf Aug 16, 2022
27469bd
Use printOnFailure
greglittlefield-wf Aug 16, 2022
c5c8401
Fix mapping ambiguity by not ignoring line numbers in error messages,…
greglittlefield-wf Aug 16, 2022
ce5327e
Implement fix for missing dependencies list
greglittlefield-wf Aug 16, 2022
226868c
Cleanup
greglittlefield-wf Aug 16, 2022
2688508
Clean up preamble
greglittlefield-wf Aug 16, 2022
d7f9aee
Uncomment most useTransition code
greglittlefield-wf Aug 17, 2022
60e955f
Add debug:over_react_metrics flag to show time spent in diagnostics
greglittlefield-wf Aug 17, 2022
2379d80
Better handling and error message when StateHook is in dependencies list
greglittlefield-wf Aug 17, 2022
399df9b
Improve messages when StateHook is a dependency
greglittlefield-wf Aug 18, 2022
97af0f8
Add test cases for StateHook in the dependency list
greglittlefield-wf Aug 18, 2022
7fd055f
Fix edge cases when StateHook is a dependency
greglittlefield-wf Aug 18, 2022
e716660
Add fallback case for when whole hooks are passed as dependencies
greglittlefield-wf Aug 18, 2022
f33d63b
Clean up comment
greglittlefield-wf Aug 18, 2022
8745938
Generalize and consolidate StateHook dependency handling into constru…
greglittlefield-wf Aug 19, 2022
3410334
Address some fixmes
greglittlefield-wf Oct 4, 2022
f1b3c85
Fix memoized builder invocation showing as an always-changing dep
greglittlefield-wf Oct 4, 2022
b610516
Address fixme, add more StateHook dependency test cases
greglittlefield-wf Oct 4, 2022
946f99f
Downgrade FIXME around sub-properties to TODO, update test
greglittlefield-wf Oct 5, 2022
b46d6b1
Reorganize hook constants into a class
greglittlefield-wf Oct 5, 2022
907bc06
Address fixmes/todos and clean up logic when hook is a dependency
greglittlefield-wf Oct 5, 2022
2234b46
Handle generic type references properly
greglittlefield-wf Oct 5, 2022
f22e911
Clean up fixmes
greglittlefield-wf Oct 5, 2022
2cbf3eb
Address more todos/fixmes
greglittlefield-wf Oct 5, 2022
4518047
Clean up fixmes
greglittlefield-wf Oct 6, 2022
c3af85a
Split out lookup functions, clean up lookUpVariable, add tests
greglittlefield-wf Oct 6, 2022
72a9b93
Clean up more todos/fixmes
greglittlefield-wf Oct 6, 2022
58390da
Improve function prop calling test coverage
greglittlefield-wf Oct 6, 2022
f5b797d
Add missing test cccase
greglittlefield-wf Oct 6, 2022
49b9a3d
Clean up more fixmes/todos
greglittlefield-wf Oct 7, 2022
4e3875c
Add tests for getNodeWithoutReactNamespace/getReactiveHookCallbackIndex
greglittlefield-wf Oct 7, 2022
fe3f518
Remove old test case
greglittlefield-wf Oct 10, 2022
6fcd3e2
Also handle local functions in useCallback quick fix, update tests
greglittlefield-wf Oct 11, 2022
3e4e733
Preserve dependencies list formatting when replacing it, add tests
greglittlefield-wf Oct 11, 2022
46a55da
Replace forEach calls with for-loops
greglittlefield-wf Oct 12, 2022
23503d1
Clean up unused comments and functions, fix typos
greglittlefield-wf Oct 12, 2022
175da0e
Indicate which TODOs were ported over, more consistent formatting
greglittlefield-wf Oct 13, 2022
ec7e865
Improve behavior around cascades, add tests
greglittlefield-wf Oct 14, 2022
9c7c64e
Clean up cascade comments
greglittlefield-wf Oct 17, 2022
727e810
Add test cases from FIXMEs
greglittlefield-wf Oct 17, 2022
b3eaa84
Fix missing List.length setter in MockSdk
greglittlefield-wf Oct 17, 2022
79d26bd
Fix test case
greglittlefield-wf Oct 17, 2022
9efb442
Fix bad non-null operator
greglittlefield-wf Oct 17, 2022
e3e4728
Clean up todo, fix typo
greglittlefield-wf Oct 17, 2022
031fb4b
Mark TODOs ported from JS implementation
greglittlefield-wf Oct 17, 2022
74f7f0b
Add test case
greglittlefield-wf Oct 17, 2022
23d8b8f
Rename test cases file, add attribution
greglittlefield-wf Oct 17, 2022
d4ddda7
Document exhaustive deps diagnostic
greglittlefield-wf Oct 18, 2022
6bb0736
Misc cleanup
greglittlefield-wf Oct 18, 2022
0e7695e
Use a single implementation for debug comments, document in README
greglittlefield-wf Oct 18, 2022
00df41e
Clean up PluginOptionsReader
greglittlefield-wf Oct 18, 2022
a7bf400
Downgrade fixme
greglittlefield-wf Oct 18, 2022
9645dd0
Fix plugin infos being filtered out in tests
greglittlefield-wf Oct 18, 2022
fbaab79
Format
greglittlefield-wf Oct 19, 2022
5a12b43
Simplify WeakMap implementation
greglittlefield-wf Oct 19, 2022
a64958e
Cleanup, add doc comments
greglittlefield-wf Oct 19, 2022
5f09a7d
Add exhaustive dependencies playground file
greglittlefield-wf Oct 19, 2022
9adb1ed
Show percentages in diagnostic metrics
greglittlefield-wf Oct 19, 2022
e2a2873
Compute debug hint strings only when needed
greglittlefield-wf Oct 19, 2022
967ccc6
Fix typo
greglittlefield-wf Nov 8, 2022
b58e5c6
Cleanup
greglittlefield-wf Nov 8, 2022
2f264f3
Add a few more playground cases, add link to ticket
greglittlefield-wf Nov 8, 2022
40d681a
Cleanup, format
greglittlefield-wf Nov 8, 2022
2a33b8f
Adjust exhaustive dependencies names to better align with ESLint
greglittlefield-wf Nov 8, 2022
76baa6b
Add more links, add more info to details section
greglittlefield-wf Nov 29, 2022
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
15 changes: 15 additions & 0 deletions tools/analyzer_plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ This script sets up a symlink to point to the original plugin directory (replaci
* If you need to get the source for a replacement, use `sourceFile.getText(node.offset, node.end)`.

### Debugging the Plugin

#### Showing Debug Info

Via `AnalyzerDebugHelper`, it's possible for diagnostics to show debug information for specific locations in a file
as infos, which can be helpful to develop/troubleshoot
issues without having to attach a debugger.

Some parts of the plugin show this debug info based on the presence of a comment anywhere in the file.
Currently-available debug info:
- `// debug: over_react_boilerplate`- shows how over_react boilerplate will be parsed/detected by the over_react
builder and analyzer functionality dealing with component declarations
- `// debug: over_react_metrics` - shows performance data on how long diagnostics took to run
- `// debug: over_react_exhaustive_deps` - shows info on how dependencies were detected/interpreted

#### Attaching a Debugger
The dev experience when working on this plugin isn't ideal (See the `analyzer_plugin` debugging docs [for more information](https://github.com/dart-lang/sdk/blob/master/pkg/analyzer_plugin/doc/tutorial/debugging.md)), but it's possible debug and see logs from the plugin.

These instructions are currently for JetBrains IDEs (IntelliJ, WebStorm, etc.) only.
Expand Down
2 changes: 2 additions & 0 deletions tools/analyzer_plugin/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ analyzer:
prefer_single_quotes: ignore
type_annotate_public_apis: ignore
prefer_final_locals: ignore
# This is less readable in some cases, and isn't worth it.
join_return_with_assignment: ignore

linter:
rules:
Expand Down
22 changes: 20 additions & 2 deletions tools/analyzer_plugin/lib/src/analysis_options/parse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:yaml/yaml.dart';

/// Parses the given yaml and returns over react analyzer plugin configuration options.
PluginAnalysisOptions? processAnalysisOptionsFile(String fileContents) {
// TODO catch parse errors, add test coverage
final yaml = loadYamlNode(fileContents);
if (yaml is YamlMap) {
return _parseAnalysisOptions(yaml);
Expand All @@ -15,14 +16,31 @@ PluginAnalysisOptions? processAnalysisOptionsFile(String fileContents) {
PluginAnalysisOptions? _parseAnalysisOptions(YamlMap yaml) {
final dynamic overReact = yaml['over_react'];
if (overReact is YamlMap) {
final errors = _parseErrors(overReact);
return PluginAnalysisOptions(errors: errors);
return PluginAnalysisOptions(
errors: _parseErrors(overReact),
exhaustiveDepsAdditionalHooksPattern: _parseExhaustiveDepsAdditionalHooksPattern(overReact),
);
}

// If there is no `over_react` key in the yaml file, return null.
return null;
}

RegExp? _parseExhaustiveDepsAdditionalHooksPattern(YamlMap overReact) {
final dynamic exhaustiveDeps = overReact['exhaustive_deps'];
if (exhaustiveDeps is! YamlMap) return null;

final dynamic additionalHooks = exhaustiveDeps['additional_hooks'];
if (additionalHooks is! String) return null;

try {
// This will throw if the regex is invalid.
return RegExp(additionalHooks);
} catch (_) {
return null;
}
}

Map<String, AnalysisOptionsSeverity> _parseErrors(YamlMap overReact) {
final dynamic errors = overReact['errors'];
if (errors is YamlMap) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/// An object containing the data under the `over_react` key in a analysis_options.yaml file.
class PluginAnalysisOptions {
final Map<String, AnalysisOptionsSeverity> errors;
PluginAnalysisOptions({required this.errors});
final RegExp? exhaustiveDepsAdditionalHooksPattern;

PluginAnalysisOptions({
required this.errors,
this.exhaustiveDepsAdditionalHooksPattern,
});
}

enum AnalysisOptionsSeverity {
Expand Down
37 changes: 18 additions & 19 deletions tools/analyzer_plugin/lib/src/analysis_options/reader.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import 'package:analyzer/dart/analysis/context_root.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:over_react_analyzer_plugin/src/analysis_options/plugin_analysis_options.dart';
import 'package:analyzer/file_system/file_system.dart' as analyzer_fs;
import 'package:over_react_analyzer_plugin/src/analysis_options/parse.dart';
import 'package:over_react_analyzer_plugin/src/analysis_options/plugin_analysis_options.dart';

/// An analysis_options.yaml reader that parses the appropriate analysis_options.yaml file for the given
/// [ResolvedUnitResult] and returns the configuration options for the over react analyzer plugin.
/// An analysis_options.yaml reader that parses the appropriate analysis_options.yaml file
/// and returns the configuration options for the over react analyzer plugin.
///
/// The reader uses caching to reduce the number of file reads. If a result is given that uses the same
/// analysis_options.yaml as a previous result, the reader will return a cache version.
class AnalysisOptionsReader {
final _cachedAnalysisOptions = <String, PluginAnalysisOptions?>{};
class PluginOptionsReader {
final _cachedOptions = <String, PluginAnalysisOptions?>{};

PluginAnalysisOptions? getAnalysisOptionsForResult(ResolvedUnitResult result) {
final file = result.session.analysisContext.contextRoot.optionsFile;
if (file != null) {
return _getAnalysisOptionForFilePath(file);
}
PluginAnalysisOptions? getOptionsForContextRoot(ContextRoot root) {
final file = root.optionsFile;
if (file == null) return null;

// There is no analysis_options.yaml, so return null.
return null;
return _getOptionsFromOptionsFile(file);
}

PluginAnalysisOptions? _getAnalysisOptionForFilePath(File file) {
return _cachedAnalysisOptions.putIfAbsent(file.path, () => _readAnalysisOptionForFilePath(file));
}
PluginAnalysisOptions? getOptionsForResult(ResolvedUnitResult result) =>
getOptionsForContextRoot(result.session.analysisContext.contextRoot);

PluginAnalysisOptions? _readAnalysisOptionForFilePath(File file) {
final fileContents = file.readAsStringSync();
return processAnalysisOptionsFile(fileContents);
PluginAnalysisOptions? _getOptionsFromOptionsFile(analyzer_fs.File file) {
return _cachedOptions.putIfAbsent(file.path, () {
if (!file.exists) return null;
return processAnalysisOptionsFile(file.readAsStringSync());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ void addUseOrCreateRef(
}

if (refTypeToReplace == RefTypeToReplace.callback) {
if (createRefField == null) return;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This null-check was added since the first arg to lookUpVariable was made non-nullable.

// Its a callback ref - meaning there is an existing field we need to update.
final declOfVarRefIsAssignedTo = lookUpVariable(createRefField, result.unit);
final declOfVarRefIsAssignedTo = lookUpVariable(createRefField, result.unit!);
if (declOfVarRefIsAssignedTo == null) return;

final nodeToReplace =
Expand Down
51 changes: 46 additions & 5 deletions tools/analyzer_plugin/lib/src/async_plugin_apis/diagnostic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
import 'package:analyzer_plugin/protocol/protocol_generated.dart';

// ignore: implementation_imports
import 'package:analyzer_plugin/src/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:meta/meta.dart';
import 'package:over_react_analyzer_plugin/src/async_plugin_apis/error_severity_provider.dart';
import 'package:over_react_analyzer_plugin/src/analysis_options/error_severity_provider.dart';
import 'package:over_react_analyzer_plugin/src/analysis_options/reader.dart';
import 'package:over_react_analyzer_plugin/src/diagnostic/analyzer_debug_helper.dart';
import 'package:over_react_analyzer_plugin/src/diagnostic_contributor.dart';
import 'package:over_react_analyzer_plugin/src/error_filtering.dart';
import 'package:over_react_analyzer_plugin/src/util/pretty_print.dart';

mixin DiagnosticMixin on ServerPlugin {
final AnalysisOptionsReader _analysisOptionsReader = AnalysisOptionsReader();
PluginOptionsReader get pluginOptionsReader;

List<DiagnosticContributor> getDiagnosticContributors(String path);

Expand All @@ -62,7 +65,7 @@ mixin DiagnosticMixin on ServerPlugin {
/// Computes errors based on an analysis result, notifies the analyzer, and
/// then returns the list of errors.
Future<List<AnalysisError>> getAllErrors(ResolvedUnitResult analysisResult) async {
final analysisOptions = _analysisOptionsReader.getAnalysisOptionsForResult(analysisResult);
final analysisOptions = pluginOptionsReader.getOptionsForResult(analysisResult);

try {
// If there is no relevant analysis result, notify the analyzer of no errors.
Expand Down Expand Up @@ -93,7 +96,7 @@ mixin DiagnosticMixin on ServerPlugin {
Future<plugin.EditGetFixesResult> handleEditGetFixes(plugin.EditGetFixesParams parameters) async {
// We want request errors to propagate if they throw
final request = await _getFixesRequest(parameters);
final analysisOptions = _analysisOptionsReader.getAnalysisOptionsForResult(request.result);
final analysisOptions = pluginOptionsReader.getOptionsForResult(request.result);

try {
final generator = _DiagnosticGenerator(
Expand All @@ -118,11 +121,11 @@ mixin DiagnosticMixin on ServerPlugin {
return DartFixesRequestImpl(resourceProvider, offset, [], result);
}

// from DartFixesMixin
// from DartFixesMixin
// List<AnalysisError> _getErrors(int offset, ResolvedUnitResult result) {
// LineInfo lineInfo = result.lineInfo;
// int offsetLine = lineInfo.getLocation(offset).lineNumber;
// these errors don't include ones from the plugin, which doesn't seem right...
// these errors don't include ones from the plugin, which doesn't seem right...
// return result.errors.where((AnalysisError error) {
// int errorLine = lineInfo.getLocation(error.offset).lineNumber;
// return errorLine == offsetLine;
Expand All @@ -135,6 +138,8 @@ mixin DiagnosticMixin on ServerPlugin {
/// a given result unit or fixes request.
@sealed
class _DiagnosticGenerator {
static final _metricsDebugCommentPattern = getDebugCommentPattern('over_react_metrics');

/// Initialize a newly created errors generator to use the given
/// [contributors].
_DiagnosticGenerator(this.contributors, {required ErrorSeverityProvider errorSeverityProvider})
Expand Down Expand Up @@ -203,15 +208,26 @@ class _DiagnosticGenerator {
// TODO: collect data how long this takes.
List<FluentComponentUsage> getUsages() => _usages ??= getAllComponentUsages(unitResult.unit!);

/// A mapping of diagnostic names to their durations, in microseconds.
final diagnosticMetrics = <String, int>{};

final metricsDebugFlagMatch = _metricsDebugCommentPattern.firstMatch(unitResult.content ?? '');

final totalStopwatch = Stopwatch()..start();
final disabledCheckStopwatch = Stopwatch()..start();

for (final contributor in contributors) {
disabledCheckStopwatch.start();
final isEveryCodeDisabled = contributor.codes.every(
(e) => _errorSeverityProvider.isCodeDisabled(e.name),
);
disabledCheckStopwatch.stop();
if (isEveryCodeDisabled) {
// Don't compute errors if all of the codes provided by the contributor are disabled
continue;
}

final contributorStopwatch = Stopwatch()..start();
try {
if (contributor is ComponentUsageDiagnosticContributor) {
await contributor.computeErrorsForUsages(unitResult, collector, getUsages());
Expand All @@ -221,6 +237,31 @@ class _DiagnosticGenerator {
} catch (exception, stackTrace) {
notifications.add(PluginErrorParams(false, exception.toString(), stackTrace.toString()).toNotification());
}
contributorStopwatch.stop();
if (metricsDebugFlagMatch != null) {
diagnosticMetrics[contributor.runtimeType.toString()] = contributorStopwatch.elapsedMicroseconds;
}
}

totalStopwatch.stop();
if (metricsDebugFlagMatch != null) {
String formatMicroseconds(int microseconds) =>
'${(microseconds / Duration.microsecondsPerMillisecond).toStringAsFixed(3)}ms';
String asPercentageOfTotal(int microseconds) =>
'${(microseconds / totalStopwatch.elapsedMicroseconds * 100).toStringAsFixed(1)}%';

final message = 'OverReact Analyzer Plugin diagnostic metrics (current file): ' +
prettyPrint(<String, String>{
...diagnosticMetrics.map((name, microseconds) =>
MapEntry(name, '${formatMicroseconds(microseconds)} (${asPercentageOfTotal(microseconds)})')),
'Total': formatMicroseconds(totalStopwatch.elapsedMicroseconds),
'Diagnostic code disabled checks': formatMicroseconds(disabledCheckStopwatch.elapsedMicroseconds),
'Loop overhead (Total - SUM(Diagnostics))': formatMicroseconds(totalStopwatch.elapsedMicroseconds -
disabledCheckStopwatch.elapsedMicroseconds -
diagnosticMetrics.values.fold(0, (a, b) => a + b)),
});
AnalyzerDebugHelper(unitResult, collector, enabled: true).logWithLocation(
message, unitResult.location(offset: metricsDebugFlagMatch.start, end: metricsDebugFlagMatch.end));
}

final filteredErrors = _configureErrorSeverities(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,14 @@ class AnalyzerDebugHelper {
collector.addError(code, location, errorMessageArgs: [message]);
}
}

/// Returns a pattern that matches a file with a `// debug:` comment with the given [debugCode].
///
/// Useful for conditionally enabling [AnalyzerDebugHelper] infos based on the presence of a comment
/// in a file.
///
/// For example, `getDebugCommentPattern('foo')` will match files containing the following comments:
///
/// - `// debug: foo`
/// - `//debug:foo,bar`
RegExp getDebugCommentPattern(String debugCode) => RegExp(r'//\s*debug:.*\b' + RegExp.escape(debugCode) + r'\b');
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import 'package:over_react_analyzer_plugin/src/over_react_builder_parsing.dart'
import 'package:over_react_analyzer_plugin/src/util/boilerplate_utils.dart';
import 'package:source_span/source_span.dart';

import 'analyzer_debug_helper.dart';

const _errorDesc = 'Something is malformed in your component boilerplate.';
// TODO: Add a more detailed description about the types of things our validator catches
// <editor-fold desc="Error Documentation Details">
Expand Down Expand Up @@ -59,7 +61,7 @@ class BoilerplateValidatorDiagnostic extends DiagnosticContributor {
static final invalidGeneratedPartFixKind = FixKind(errorCode.name, 200, 'Fix generated part directive');
static final unnecessaryGeneratedPartFixKind = FixKind(errorCode.name, 200, 'Remove generated part directive');

static final _debugFlagPattern = RegExp(r'debug:.*\bover_react_boilerplate\b');
static final _debugCommentPattern = getDebugCommentPattern('over_react_boilerplate');

// TODO(nullsafety) come back and clean up fields

Expand Down Expand Up @@ -89,7 +91,7 @@ class BoilerplateValidatorDiagnostic extends DiagnosticContributor {
/// Also returns whether the component has valid over_react declarations, which is useful in determining whether to
/// validate the generated part directive.
Future<bool> _computeBoilerplateErrors(ResolvedUnitResult result, DiagnosticCollector collector) async {
final debugMatch = _debugFlagPattern.firstMatch(result.content!);
final debugMatch = _debugCommentPattern.firstMatch(result.content!);
final debug = debugMatch != null;
if (debug) {
collector.addError(debugCode, result.location(offset: debugMatch!.start, end: debugMatch.end),
Expand Down
Loading