Skip to content

Implementation of parameter parser#234

Open
Dariaa14 wants to merge 12 commits intoanalysis_server_migrationfrom
implementation-of-parameter-parser
Open

Implementation of parameter parser#234
Dariaa14 wants to merge 12 commits intoanalysis_server_migrationfrom
implementation-of-parameter-parser

Conversation

@Dariaa14
Copy link
Copy Markdown
Collaborator

Implementation:

Since the analysis_server_plugin currently lacks a direct way to retrieve the context root (noted by the TODO in the official source code), I explored two strategies for resolving analysis_options.yaml:

  1. Plugin Register: I found this too restrictive, as it only functions correctly when 'dart analyze' is executed from the project root.

  2. Rule Registration with Caching (Chosen): By resolving the configuration at the rule level, we can leverage the RuleContext to identify the file path currently under analysis. To prevent the performance overhead of re-parsing for every file, I implemented a caching layer. The YAML is only parsed once per 'dart analyze'; subsequent files retrieve the configuration from the cache.

Research of other packages:

I researched the approach of others:

custom_lint and dart_code_metrics both utilize a custom server architecture built on the legacy analyzer_plugin package.
I found no existing public implementations for dynamic parameter injection for analysis_server_plugin users.

Configuration Structure

Regarding our previous discussion on YAML structure: I tested the older formats used by other analyzers, but they were not recognized by the current server plugin. I have reverted to the structure specified in the official Dart documentation, which I have verified as functional in this implementation.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the AnalysisOptionsLoader and LintOptions classes to parse and manage lint configurations from analysis_options.yaml. The review identifies critical issues with the current caching strategy, noting that a global cache will cause incorrect behavior in monorepos and lacks a mechanism for invalidation when configuration files change. Further recommendations include renaming variables for clarity and consistently using the analyzer's ResourceProvider API instead of dart:io to ensure better compatibility and testability.

Comment on lines +24 to +26
if (_rulesCache.isNotEmpty) {
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The global _rulesCache and the early return when it is not empty will cause incorrect behavior in multi-package workspaces (monorepos). If different packages have different analysis_options.yaml configurations, the plugin will incorrectly apply the configuration of the first package it analyzes to all subsequent packages. The cache should be keyed by the configuration file path or the context root to ensure each package uses its own settings.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Dariaa14, I think this one might be relevant as well.


/// Loads and parses analysis options from a Dart project's YAML file.
class AnalysisOptionsLoader {
Map<String, LintOptions> _rulesCache = {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current caching mechanism does not support cache invalidation. In a long-running Analysis Server session (e.g., in an IDE), changes to analysis_options.yaml will not be detected, and the plugin will continue using stale configuration until the server is restarted. Consider checking the file's modification timestamp or implementing a file watcher to refresh the cache when the configuration changes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Dariaa14, have you investigated the problem described here? Looks pretty relevant overall, but I'm not sure if changing analysis_options.yaml would restart the analysis server and reload the configuration.

Comment on lines +30 to +31
final directory = context.allUnits.first.file.path;
_loadRules(directory);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The variable name directory is misleading as it holds the path to a file, not a directory. Renaming it to filePath would improve code clarity.

Suggested change
final directory = context.allUnits.first.file.path;
_loadRules(directory);
final filePath = context.allUnits.first.file.path;
_loadRules(filePath);

Comment on lines +51 to +69
final startFile = io.File(filePath);
io.Directory dir = startFile.parent;

while (true) {
final candidate = PhysicalResourceProvider.INSTANCE
.getFile('${dir.path}${Platform.pathSeparator}$fileName');

if (candidate.exists) {
return candidate.path;
}

final parent = dir.parent;

if (parent.path == dir.path) {
return null;
}

dir = parent;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Avoid mixing dart:io classes with the analyzer's ResourceProvider abstraction. Using the ResourceProvider API consistently ensures the plugin works correctly in all environments, including tests with MemoryResourceProvider. Additionally, use pathContext.join instead of manual string interpolation for platform-independent path construction.

    final pathContext = PhysicalResourceProvider.INSTANCE.pathContext;
    var currentDir = pathContext.dirname(filePath);

    while (true) {
      final candidatePath = pathContext.join(currentDir, fileName);
      if (PhysicalResourceProvider.INSTANCE.getFile(candidatePath).exists) {
        return candidatePath;
      }

      final parentDir = pathContext.dirname(currentDir);
      if (parentDir == currentDir) {
        return null;
      }

      currentDir = parentDir;
    }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Dariaa14, please consider this one as well.

Copy link
Copy Markdown
Collaborator

@solid-vovabeloded solid-vovabeloded left a comment

Choose a reason for hiding this comment

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

Great start, @Dariaa14! Please, take a look at the following suggestions and let me know what you think.


/// Loads and parses analysis options from a Dart project's YAML file.
class AnalysisOptionsLoader {
Map<String, LintOptions> _rulesCache = {};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Dariaa14, have you investigated the problem described here? Looks pretty relevant overall, but I'm not sure if changing analysis_options.yaml would restart the analysis server and reload the configuration.

Comment on lines +11 to +12
/// A global instance of [AnalysisOptionsLoader] for use across the plugin.
final analysisLoader = AnalysisOptionsLoader();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please, avoid using top-level variables. I believe it would be better to instantiate this class during plugin creation and then inject it wherever we need.

Comment on lines +24 to +26
if (_rulesCache.isNotEmpty) {
return;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Dariaa14, I think this one might be relevant as well.

Comment on lines +51 to +69
final startFile = io.File(filePath);
io.Directory dir = startFile.parent;

while (true) {
final candidate = PhysicalResourceProvider.INSTANCE
.getFile('${dir.path}${Platform.pathSeparator}$fileName');

if (candidate.exists) {
return candidate.path;
}

final parent = dir.parent;

if (parent.path == dir.path) {
return null;
}

dir = parent;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@Dariaa14, please consider this one as well.

_rulesCache = rules;
}

String? _findNearestYamlUpwards(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
String? _findNearestYamlUpwards(
String? _findNearestFileUpwards(

I believe it may be used for searching other files as well.

final startFile = io.File(filePath);
io.Directory dir = startFile.parent;

while (true) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
while (true) {
while (dir.path != dir.parent.path) {

Does that make sense?

}
}

return UnmodifiableMapView(rules);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we are using UnmodifiableMapView, then we probably should have _rulesCache of UnmodifiableMapView type.

Map<String, LintOptions> _rulesCache = {};

/// Retrieves the currently loaded lint rules.
Map<String, LintOptions> get rules => _rulesCache;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it be great to have a method to get the options for the exact rule name?

Comment on lines +215 to +219
/// Whether the configuration enables/disables the lint rule.
final bool enabled;

/// Extra configurations for a [AnalysisRule].
final Map<String, Object?> json;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please, declare fields and getters before the constructor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants