Flutter grammar detection and principle analysis-FAIR grammar detection practice

image.png

foreword

Flutter is Google's mobile UI framework for quickly building high-quality native user interfaces on iOS and Android.

Fair is a dynamic framework of Flutter open sourced by 58 technology, which can realize the dynamic of UI and logic.image.png

image.png

Developers have some pain points in the process of using Fair to develop, for example, there may be incorrect use of syntactic sugar or unsupported syntactic sugar, so we need a supporting plug-in to prompt users to use Fair syntactic sugar.

1. Flutter syntax detection mechanism

In the IDE, the Flutter syntax detection mechanism is implemented by relying on the Dart/Flutter plug-in, that is, the Dart/Flutter plug-in that we need to download before developing Flutter. We need to provide the development environment of Flutter through plugins, and plugins can also provide syntax detection. One of the core functions of the plug-in is Analysis Server.

What is Analysis Server?

Analysis Server is a Dart/Flutter syntax analysis service provided by the Dart SDK. Its main functions include syntax static analysis, code hints, and code completion. Our commonly used Dart/Flutter IDEs, such as intellij, Android Studio and VS Code, all configure the Dart development environment by installing the Dart plugin.

Workflow of Dart/Flutter plugin syntax detection

Taking Android Studio as an example (because the language of the corresponding plug-in is Java, which is easier to read and understand), the core of grammar detection is Analysis Server. Whenever the user code is changed, it will be synchronized to Analysis Server through Socket. After the server analysis is completed, the analysis results will be returned, and the Dart plug-in will do different processing according to the event type returned by the Analysis Server, and finally refresh the results on the user IDE interface. The flow chart is as follows:

image.png

The startup process of Analysis Server in the Dart plugin

After the user configures the Dart SDK path, the plug-in will obtain the configuration path. When the plug-in starts, it will start a process to execute the dart executable file in the Dart SDK, and also obtain the "/bin/" in the Dart SDK directory. snapshots/analysis_server.dart.snapshot" file with its path as vmArguments. We can look at a small piece of code:


//获取配置的SDK路径
mySdkHome = sdk.getHomePath();

//找到dart可执行文件的路径
final String runtimePath = FileUtil.toSystemDependentName(DartSdkUtil.getDartExePath(sdk));

//找到analysis_server.dart.snapshot文件路径
String analysisServerPath = FileUtil.toSystemDependentName(mySdkHome + "/bin/snapshots/analysis_server.dart.snapshot");

//拼凑vmArguments
String firstArgument = useDartLangServerCall ? "language-server" : analysisServerPath;

//创建Socket
myServerSocket =
        new StdioServerSocket(runtimePath, StringUtil.split(vmArgsRaw, " "), firstArgument, StringUtil.split(serverArgsRaw, " "), debugStream);

//创建AnalysisServer实现类
final RemoteAnalysisServerImpl startedServer = new RemoteAnalysisServerImpl(myServerSocket);

//这里的具体实现其实就是socket.start(),将socket启动起来
startedServer.start();

Dart plugin interacts with Analysis Server

There are two main types of interaction between Dart plugins and Analysis Server, one is id and the other is event. Every time the Dart plugin synchronizes data to Analysis Server, it will generate a uniqueId and cache it in HashMap. Every time Analysis Server returns data, it will also bring event or id, and prioritize event type data.

Dart plugin synchronizes data to Analysis Server Example:

request: {
  "id": String
  "method": "analysis.setAnalysisRoots"
  "params": {
    "included": List<FilePath>
    "excluded": List<FilePath>
    "packageRoots": optional Map<FilePath, FilePath>
  }
}

As mentioned above, the Dart plugin will generate a uniqueId every time it synchronizes data to the Analysis Server. The generation rule here is to use the getAndIncrement() method of AtomicInteger with atomic attributes to synchronize data +1 each time. The Dart plugin implements a Consumer or Processor for each event of Analysis Server (Processor if it is event type data, and Consumer if result event type).

The mounting process of the syntax detection plug-in

The plugin has a mounting process. The mounting process of the plugin is shown in the following figure:image.png

To summarize:

  1. YamlParaser parses the analyzer#plugins node in the analysis_options.yaml file and obtains a pluginNameList
  2. Traverse the analyzer_plugin directory path obtained for each plugin through Plugin Locator according to pluginNameList, namely pluginPath
  3. Copy the pluginPath directory path file to the .dartServer/plugin_manager/unique Directory directory of the system, where the unique Directory is a string generated by md5 operation on pluginPath
  4. Execute the main method of /bin/plugin.dart under pluginPath

Second, the implementation principle of the Fair grammar detection plug-in

When we master the previous knowledge, it is much simpler to develop grammar plugins.

Implementation mechanism of Fair syntax detection plug-in

The Dart plugin implements Dart syntax detection based on Analysis Server. The Fair syntax detection plugin supplements and extends the Dart plugin syntax detection function to detect Fair syntax sugar. The Fair syntax plugin essence page is a Dart syntax detection plugin. Can be used by configuring pubspec.yaml and analysis_options.yaml.image.png

Grammar plugin development process

It can be mainly divided into the following steps:

  1. First create a class to inherit ServerPlugin and implement abstract properties/methods.
  2. Then create the tools/analyzer_plugin directory at the same level as lib, as shown in the figure:image.png
  3. Finally register the ServerPlugin implemented in step 1 in the plugin.dart file.
//示例
void main(List<String> args, SendPort sendPort) {
	ServerPluginStarter(FairPlugin(PhysicalResourceProvider.INSTANCE)).start(sendPort);
}

core implementation

The core of the Fair syntax detection plug-in is abstract syntax tree traversal. By traversing the abstract syntax tree, each child node is obtained, and then custom syntax detection logic is inserted.

1. Create AnalysisDriver

createAnalysisDriver() is an abstract method of ServerPlugin. The developer implements this method to create an AnalysisDriver, and then we can monitor the return of AnalysisResult through the results data stream of AnalysisDriver. The AnalysisResult is the analysis result of the file by Analysis Server. The sample code looks like this:

@override
AnalysisDriverGeneric createAnalysisDriver(plugin.ContextRoot contextRoot) {

	//获取contextRoot
	final contextRoots =
        ContextLocator(resourceProvider: resourceProvider).locateRoots(...);
	
	//获取ContextBuilder
	final contextBuilder =
        ContextBuilderImpl(resourceProvider: resourceProvider);
	
	//获取Analysis Context
	final context = contextBuilder.createContext(
        contextRoot: contextRoots.first, byteStore: byteStore);
	

	//获取Analysis Driver
	final dartDriver = context.driver;

	//监听分析结果
	dartDriver.results.listen((analysisResult) {_processResult(...);});
	return dartDriver;
}

2. Sensing code changes

ServerPlugin provides the contentChanged method, we only need to add the file path with changed content to AnalysisDriver when receiving the callback

@override
void contentChanged(String path) {
  super.driverForPath(path)?.addFile(path);
}

3. Use AOP idea to process AnalysisResult

Dart's abstract syntax tree traversal is implemented through the visitor pattern. We can insert a custom Visitor at the node we want to process to access it and its child nodes. Among them, we can get the compilation unit (CompilationUnit) from AnalysisResult, each compilation unit is an abstract syntax tree, and we can insert a Visitor through its accept() method.

4. @FairPatch annotation identifies implementation logic

We know that the Dart abstract syntax tree is implemented through the visitor pattern, so we can easily implement custom logic. To realize the recognition of the @FairPatch annotation, we only need to judge that the annotation name is the FairPatch we want when traversing the Annotation node. The sample code is as follows:

class _FairVisitor extends RecursiveAstVisitor<void> {

  @override
  void visitAnnotation(Annotation node) {
    node.visitChildren(this);
    if (node.name.name == \'FairPatch\') {
      //...
    }
  }
}

5.if syntax detection

Because if syntax detection requires preconditions, first of all, it must be a class marked with @FairPatch, and secondly, it needs to be detected by the if under the build() method.

  • The first is the build() method detection, which only needs to be filtered during method deceleration.
@override
void visitMethodDeclaration(MethodDeclaration node) {
	if (node.name.name == \'build\') {
		//...
	}
}

  • The second is if grammar detection, which is a little more complicated. If is divided into IfStatement and IfElement. IfStatement refers to the outer if-else statement, IfElement refers to the if-else statement nested in other grammars, if you want To fully cover the if syntax, you need to implement both.

image.png image.pngThe above two figures correspond to the specific meanings of IfStatement and IfElement respectively.

6.Fair's Sugar.if syntax sugar classification implementation

Sugar.if related syntactic sugars commonly used in Fair are Sugar.ifEqual, Sugar.ifEqualBool and Sugar.ifRange

  • Sugar.ifRange
static K ifRange<T, K>(
    T actual,
    List<T> expect, {
    required K trueValue,
    required K falseValue,
  }) =>
      expect.contains(actual) ? trueValue : falseValue;
  • Sugar.ifEqual
static K ifEqual<T, K>(
    T actual,
    T expect, {
    required K trueValue,
    required K falseValue,
  }) =>
      expect == actual ? trueValue : falseValue;
  • Sugar.ifEqualBool
static K ifEqualBool<T, K>(
    bool state, {
    required K trueValue,
    required K falseValue,
  }) =>
      state ? trueValue : falseValue;

Using Fair syntactic sugar can speed up the compilation of Fair, so we all recommend using syntactic sugar if syntactic sugar can be used. We want to classify syntactic sugar, which is actually to distinguish the conditions of if, of which ifRange is a little more complicated, then we will Let's talk about how to achieve it, and the same is true for others:

//ifRange有个前置条件就是如参expect是一个List,所以再检测的时候只需要判断用户在调用list.contains(xx)即可

//先判断用户是在调用contains()方法
  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    if (node.name.name == \'contains\') {
      //...
    }
  }


//其次再判断调用contains方法的对象是List
  @override
  void visitMethodInvocation(MethodInvocation node) {
    super.visitMethodInvocation(node);
     
    //判断staticType即可
    _result = node.target?.staticType?.isDartCoreList ?? false;
    if (_result) {
      _target = node.target;
      _actual = node.argumentList.arguments.first;
    }
  }

3. Fair Plugin to achieve effect display

  • Android Studio implements the detection effect of the if syntax block under the build() method
  1. Code detection of if under the build method, and prompt guidance information
    image.png
  2. Click more action or AS code prompt shortcut key
    image.png
  3. Click Replace as prompted
    image.png

related suggestion

Support us

Welcome everyone to use Fair, and welcome everyone to light up the star for us

Github address: https://github.com/wuba/fair
Fair official website: https://fair.58.com

Contributions welcome

Submit issues through Issue , and please submit a Pull Request to contribute code, and the administrator will review the code.

Friends who are interested in Fair can join the exchange group.

WeChat
wechat

WeChat group: Please add 58 technical secretary as a friend first, note fair, and the secretary invites you to join the group.

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/5359019/blog/5585287