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.
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:
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:
To summarize:
- YamlParaser parses the analyzer#plugins node in the analysis_options.yaml file and obtains a pluginNameList
- Traverse the analyzer_plugin directory path obtained for each plugin through Plugin Locator according to pluginNameList, namely pluginPath
- 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
- 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.
Grammar plugin development process
It can be mainly divided into the following steps:
- First create a class to inherit ServerPlugin and implement abstract properties/methods.
- Then create the tools/analyzer_plugin directory at the same level as lib, as shown in the figure:
- 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.
The 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
- Code detection of if under the build method, and prompt guidance information
- Click more action or AS code prompt shortcut key
- Click Replace as prompted
related suggestion
- Flutter dynamic framework Fair document online & open source countdown
- Design and thinking of Flutter's dynamic framework Fair
- Fair 2.0 logic dynamic open source!
- Fair logic dynamic architecture design and implementation
- Fair logic dynamic communication implementation
- Fair issued product-layout DSL generation principle
- Fair logic syntactic sugar design and implementation
- Fair hot update design and implementation
- Fair's practice in the Anju Paifang App
- Flutter dynamic project evaluation
- Fair's practice in the 58.com app
- Flutter + Dart three-terminal integrated dynamic platform practice
- Super comprehensive Flutter performance optimization practice
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 group: Please add 58 technical secretary as a friend first, note fair, and the secretary invites you to join the group.