精华内容
下载资源
问答
  • IDEA - 2020.1 版本针对调试器和代码分析器的改进,值得期待1、对于调试器的加强:数据流分析辅助2、调试加强:属性置顶功能3、调试加强:IPV6 调试4、性能分析的改进,剔除额外的东西5、支持读取内存快照文件6、...


    1、对于调试器的加强:数据流分析辅助

    IntelliJIDEA v2020.1向调试器添加数据流分析辅助,它根据程序执行的当前状态预测和显示可能的异常,并始终为真/始终为假条件。

    调试Java代码并到达断点时,IDE将根据程序的当前状态运行数据流分析,并在代码执行达到此点之前向您展示下一步将发生什么

    简单点说,就是在调试那些复杂的代码时,IDE可以预先显示不变的那些调试值,让你能够更好的调试代码,如图
    在这里插入图片描述

    2、调试加强:属性置顶功能

    这个改进不大,但是调试的时候很有用,就是说,你在调试的时候呢,有些对象的字段太多了,要去找他有时候还要翻页或者下拉很多,一般我们调试可能要走好多遍代码,你现在在第一次调试后,吧这个调试的字段置顶,以免老是要去找。

    在这里插入图片描述

    3、调试加强:IPV6 调试

    现在IDEA 支持通过远程计算机 IPV6 进入到调试会话

    4、性能分析的改进,剔除额外的东西

    使用CPU 调试器进行性能分析的时候呢,可以通过隐藏一些方法啊什么的,或者只关注某个调用节点下的方法,来提供更高关注度的分析

    提供了四个选项
    1、只关注子集调用
    2、只关注本调用
    3、屏蔽子集调用
    4、屏蔽本调用
    在这里插入图片描述

    另外,IDEA 允许你绕过递归,让你能够进行更专注的性能分析

    在这里插入图片描述

    5、支持读取内存快照文件

    IDEA 现在支持打开 hprof 文件,也就是内存快照文件,并且打开内存快照文件不会占用你太多的内存,如果你要打开这种文件,你需要如图所示

    在这里插入图片描述
    打开后呢,是这样的

    在这里插入图片描述

    就是暂时呢,只能进行简单的分析,后续的功能还在开发当中

    6、IDEA 变更了代码提交的界面

    他大概是这个样子的

    在这里插入图片描述

    7、LightEdit 用来作为简单的文本编辑器

    看着是个好功能,但是感觉还是有点鸡肋吧,因为他要通过命令行模式打开,并且有一定的功能阉割,然后就是打开快一点。

    在这里插入图片描述

    8、可以预览变更意图了

    意思就是呢,我们通过快捷键可以打开一些IDEA 提供的建议,比如这里有个警告,IDEA 会提供一些建议的解决方案或者让你修改设置,现在呢,如果是需要更改代码,在改之前,IDEA 可以让你预览一下改完了是个啥样子,不需要先改完,然后再取消了。

    如果你不改快捷键呢,就是alt +空格了

    9、禅定模式

    用来消除分心的,解决之前的全屏模式的一些不足,让现在更好用了。

    展开全文
  • C#代码分析器(C#游戏代码注入)

    千次阅读 2017-03-06 12:50:55
    reflector可以方便的进行代码注入,但是如果注入逻辑比较复杂的话,就需要添加多个函数,比较麻烦。 这里的代码分析器可以方便的把多个函数整理成一个函数,方便进行代码的注入

    简介

      在进行C#代码注入的过程中,写的比较复杂的逻辑,但是reflector注入只能一个个函数来进行,而且添加paramter非常复杂,这里做了一个简单的代码分析器,帮助所有代码集中到一个函数中。

          虽然感觉上比较简单,但做的过程中,花费了大半天的时间,这里把遇到的问题总结一下,给大家后续开发一些启示。

      理论上C语言格式的都可以用这个解析把多个函数包含在一个函数里面


    Reflector注入过程

    • 1 导入相应的dll
    • 2 tools decompile。 点右键 replaceWithCode。
    • 3 子类添加函数基类,然后写函数。
    • 4 在dll上右键Reflexil -> SaveAs相关dll即可。

     

    遇到的问题

      因为relector一次只支持注入一个函数,而我们写的显示代码是有多个函数的。

          即使新加入一个类,也需要在这个类里面单独加入各个函数。

         同时代码里面有互相的嵌套,而且有返回值和各个参数,如果单个的加非常的麻烦,而且reflector不支持返回值,相关的值只能放入输出参数,和原有的代码就大大违背了。

    所以这里写了一个简单的代码分析器,用这个解析把多个函数包含在一个函数里面。


    代码格式

    首先读入的代码要编译通过

    需要一些简单的约束:

    • 1子函数必须有返回值,且返回值不能用和次子函数的参数相同。只有一个返回的出口。上支持上任意返回类型,只是父函数必须调用一下子函数返回的值。
    • 2 调用函数的参数中不支持嵌套调用自己写的函数。
    • 3 除非是返回值,否则临时变量需要自行设的是unique的。否则会重复定义。
    • 4 “{}”这种单独占一行,一般C#编辑器会自动解析成这样。
    • 5 函数不能循环调用,解析会进入循环。
    • 6 目前只支持+=和+,如果需要其他运算符,在tpl_func_split中加。  特殊的运算序列没有做支持,当时这个分析器只是为了输出日志方便用的。

     

    输入格式

      //(joint为已知的Joint数据结构)
            public string A(string p)
            {
                string tmpA = "add a" + p;
                return tmpA;
            }
            public string B(Jointjoint, string c)
            {
                string tmp = "add b\n";
                tmp += A(joint.format) + joint;
                return tmp;
            }
            public void Jump()
            {
                string tmp = "start ===\n";
                //tmp += "A " + A("12");
                //tmp += B("34", "12");
                tmp += " A B" + A("12") + B(joint, "c") + A("34");
            }


    输出格式

    会经过代码分析器分析为:

           public void Jump()
            {
                string tmp ="start ===\n";
                string _result_6_ ="add a"+"12";
                string _result_7_ ="add b\n";
                string _result_8_ ="add a"+joint.format;
                _result_7_ +=_result_8_+joint;
                string _result_9_ ="add a"+"34";
                tmp +=" A B"+_result_6_+_result_7_+_result_9_;
             }
     


    思路

    整体思路:

    分治法。

    每个函数有多行语句,每行语句包含多个函数,经过相互调用就相当于减了一层调用。无论有多少层调用,最终都可以解析为只包含一层可以输出的序列。

    如 tmp += A(cc)

    就会变为:

    <<A相关的表达式,同时更新所有的输入为cc输出为_result_1_>>

    Tmp += _result_1_;

      

    1 将每个函数解析为结构FuncDesc, 存入一个整体的FUNC_DICT中。

    class FuncDesc(object):
      def __init__(self):
        self.params_list = []
        self.func_desc = []
        self.func_desc_split = []
        self.func_name = ""
        self.out_name = ""

      

    2 每个out_name和params_list的表示都用一个特殊的完全不同的字符串代替。方便以后在调用的时候,整体替换。

    3 其中每个func_desc表示一行代码。

      每行代码再生成一个结构

    class DescFuncSplit(object):
      def __init__(self, desc_str):
        self.desc_str = desc_str
        # (type, funcname, params), type0:normal, 1 func
        self.func_list = []
        self.opeartor_list = []
        self.has_semicolon = False

    4 在调用到单个func_desc的时候,判断各个函数,调用到FuncDesc,赋值params和return_value。

      FuncDesc在依次调用每个func_desc_split。最终会得到一系列字符串。

      经过相互调用,最终会在最简单层中停止:

      code中A

    这一系列的字符串就是最后希望得到的函数。

    输出是最终的函数内部的内容。

    需要自己加上函数头,可以放在C#编辑器里面format一下。

     

    下附代码

    #-*- coding:utf-8 -*-
    
    """
    python XX.py inputfilename.cs out_func_name
    第一个参数是要读入的文件名称
    第二个参数是要输出的函数名
    """
    import os, sys
    import re
    
    TOTAL_NUM = 0;
    FUNC_DICT = {}
    def GetIndexNum():
      global TOTAL_NUM
      TOTAL_NUM += 1
      return TOTAL_NUM
    
    def replacer(search_obj, replace_str):
      groups = search_obj.groups()
      return groups[0] + replace_str + groups[1]
       
    def change_descs(init_str, change_param, replace_param):
      cal = r"(\W)%s(\W)" % change_param;
      replacer2 = lambda search_obj: replacer(search_obj, replace_param)
      return re.sub(cal, replacer2, init_str)
       
    class DescFuncSplit(object):
      def __init__(self, desc_str):
        self.desc_str = desc_str
        # (type, funcname, params), type0:normal, 1 func
        self.func_list = []
        self.opeartor_list = []
        self.has_semicolon = False
        
      def debug_info(self):
        print "=============="
        print "debug_info: ", self.desc_str
        print self.func_list
        print self.opeartor_list
        
      def get_split_out_result(self, param_dict):
        print "get_split_out_result" , param_dict
        total_len = len(self.func_list)
        out_str = ""
        out_list = []
        for idx in xrange(total_len):
          func_tuple = self.func_list[idx]
          if func_tuple[0] == 0:
            out_str += func_tuple[1]
          else:
            func_result_name = "_result_%s_" % GetIndexNum()
            func_desc = FUNC_DICT[func_tuple[1]]
            params = func_tuple[2]
            new_params = []
            for new_param in params:
              for k,v in param_dict.items():
                new_param = new_param.replace(k, v)
              new_params.append(new_param)
              
            tmp_list = func_desc.get_end_result(new_params, func_result_name)
            out_list.extend(tmp_list)
            out_str += func_result_name
          if idx < total_len - 1:
            out_str += self.opeartor_list[idx]
        if self.has_semicolon:
          out_str += ";"
        out_str += "\n"
        out_list.append(out_str)
        return out_list
          
      def tpl_func_split(self):
        total_split = []
        out_str = ""
        if self.desc_str.find("+=") >= 0:
          self.func_list.append((0, self.desc_str[:self.desc_str.find("+=")]))
          out_str = self.desc_str[self.desc_str.find("+=") + 2:]
          self.opeartor_list.append("+=")
        elif self.desc_str.find("=") >= 0:
          self.func_list.append((0, self.desc_str[:self.desc_str.find("=")]))
          out_str = self.desc_str[self.desc_str.find("=") + 1:]
          self.opeartor_list.append("=")
        else:
          out_str = self.desc_str
        
        #鍘婚櫎瀛楃涓插唴閮ㄧ殑+ -> _
        total_len = len(out_str)
        idx = 0
        while(idx < total_len):
          if out_str[idx] == "\"":
            idy = idx + 1
            while(idy < total_len):
              if out_str[idy] == "+":
                out_str[idy] = "_"
              if out_str[idy] == "\"":
                idy += 1
                break
              idy += 1
            idx = idy
          else:
            idx += 1
        
        func_split = out_str.split("+")
        for sp in func_split:
          sp = sp.strip()
          if sp[-1] == ";":
            sp = sp[:-1].strip()
            self.has_semicolon = True
            
          if sp[0] == "\"":
            self.func_list.append((0, sp))
            self.opeartor_list.append("+")
          elif sp[-1] == ")":
            l = sp.find("(")
            funcname = sp[:l].strip()
            if not FUNC_DICT.has_key(funcname):
              self.func_list.append((0, sp))
              self.opeartor_list.append("+")
              continue
            params_str = sp[l+1:-1]
            params_split = params_str.split(",")
            params = []
            for param in params_split:
              param = param.strip()
              params.append(param)
            self.func_list.append((1, funcname, params))
            self.opeartor_list.append("+")
          else:
            self.func_list.append((0, sp))
            self.opeartor_list.append("+")
        return
        
        
        
    class FuncDesc(object):
      def __init__(self):
        self.params_list = []
        self.func_desc = []
        self.func_desc_split = []
        self.func_name = ""
        self.out_name = ""
      
      def debug_info(self):
        print "========"
        print "params_list:", self.params_list
        print "func_desc:"
        print self.func_desc
        print "func_name: %s, out_name: %s" % (self.func_name, self.out_name)
        
      def get_func_name(self, fs):
        fp = fs.split("(")
        tmp_list = fp[0].split(" ")
        self.func_name = tmp_list[-1];
        fp[1] = fp[1].replace(")", "")
        tmp_list = fp[1].split(",")
      
        for tmp in tmp_list:
          tt = tmp.split(" ")
          for idtt in xrange(len(tt) - 1, -1, -1):
            if tt[idtt].strip() != "":
              self.params_list.append(tt[idtt].strip())   
              break 
             
      def change_params_to_unique(self):
        for params_key in xrange(len(self.params_list)):
          new_param = "_param_%s_" % GetIndexNum()
          param = self.params_list[params_key]
          
          self.params_list[params_key] = new_param
          for desc_key in xrange(len(self.func_desc)):
            desc = self.func_desc[desc_key]
            new_desc = change_descs(desc, param, new_param)
            self.func_desc[desc_key] = new_desc
        return
      
      def change_out_name_to_unique(self):
        return_desc = self.func_desc[-1]
        new_desc = return_desc.replace(";", "")
        if new_desc.find("return") < 0:
          return
        out_name = new_desc[new_desc.find("return") + 6:].strip()
        if out_name == "":
          self.func_desc.pop()
          return
        self.out_name = "_result_%s_" % GetIndexNum()
        for desc_key in xrange(len(self.func_desc)):
          desc = self.func_desc[desc_key]
          new_desc = change_descs(desc, out_name, self.out_name)
          self.func_desc[desc_key] = new_desc
        self.func_desc.pop()
        return
      
      def change_desc_value_to_func_split(self):
        for desc in self.func_desc:
          desc_func_split = DescFuncSplit(desc)
          desc_func_split.tpl_func_split()
          self.func_desc_split.append(desc_func_split)
         
      
      def get_end_result(self, params, func_result_name):
        print "get_end_result ", params, func_result_name
        if self.out_name != "" and func_result_name == "":
          print "error %s has no func_result_name" % self.func_name
          raise 1
        if len(params) != len(self.params_list):
          print "error %s params error %s %s" % (self.func_name, self.params_list, params)
          raise 1
        
        param_dict = {}
        for idx in xrange(len(self.params_list)):
          param_dict[self.params_list[idx]] = params[idx]
        out_list = []
        for func_split in self.func_desc_split:
          func_split.debug_info()
          func_str_list = func_split.get_split_out_result(param_dict)
          for func_str in func_str_list:
            out_str = func_str;
            for k,v in param_dict.items():
              out_str = out_str.replace(k,v)
            out_list.append(out_str)
    
        if self.out_name != "":
          for idx in xrange(len(out_list)):
            out_str = out_list[idx]
            out_list[idx] = out_str.replace(self.out_name, func_result_name)
        return out_list
    
    def main(filename, out_func_name):
      f = open(filename, "r")
      lines = f.readlines();
      f.close();
      total_line = len(lines)
      for line in lines:
        if line.startswith("{") or line.endswith("}"):
          if len(line.strip()) != 1:
            print "not strip one line", line
            raise 1
      cur_num = 0
      while(cur_num < total_line):
        if lines[cur_num].find("public") < 0:
          cur_num += 1
          continue
        func_desc = FuncDesc()
        func_desc.get_func_name(lines[cur_num])
        cur_num += 1
        while(cur_num < total_line):
          if lines[cur_num].strip() == "{":
            break
          cur_num += 1
          
        cur_num += 1
        cur_left = 1
        while(cur_num < total_line):
          if lines[cur_num].strip() == "{":
            cur_left += 1
          if lines[cur_num].strip() == "}":
            cur_left -= 1
            if cur_left == 0:
              break 
          strip_line = lines[cur_num].strip()
          if strip_line != "" and not strip_line.startswith("//"):
            func_desc.func_desc.append(lines[cur_num])
          cur_num += 1
    
        FUNC_DICT[func_desc.func_name] = func_desc
        cur_num += 1
      
      print FUNC_DICT.keys()
      
      for func_desc in FUNC_DICT.values():
        func_desc.change_params_to_unique()
        func_desc.change_out_name_to_unique()
        func_desc.change_desc_value_to_func_split()
        func_desc.debug_info()
      
      if not FUNC_DICT.has_key(out_func_name):
        print "error outfuncname in readfile", filename
        return
      
      out_str_list = []
      out_str_list = FUNC_DICT[out_func_name].get_end_result([], "")
      f = open("out.txt", "w")
      for out_str in out_str_list:
        f.write(out_str)
      f.close()
       
    if __name__ == "__main__":
      if len(sys.argv) < 3:
        print "please input: readfilename outfuncname"
        exit()
      out_func_name = sys.argv[2]
      filename = sys.argv[1]
      main(filename, out_func_name)


    
    


    展开全文
  • “火旋风”代码分析器作者扎克·伯恩斯发布了这款侵入式代码分析器。“火旋风”分析器能帮助代码作者测试Rust代码的性能;它能分析项目中的时间敏感部分,输出到时间轴图、合并的火焰图或其它的表现...

    “火旋风”代码分析器

    作者扎克·伯恩斯发布了这款侵入式代码分析器。“火旋风”分析器能帮助代码作者测试Rust代码的性能;它能分析项目中的时间敏感部分,输出到时间轴图、合并的火焰图或其它的表现形式。这是一款侵入式分析器,也就意味着在代码编写的过程中,用户就需要使用分析器提供的宏,帮助分析器的记录过程。项目文档指出,这款分析器能通过编译特性来启用或禁用;未被启用时,所有的记录操作都被编译为空操作,这将不会影响生产程序的运行性能。

    我们常用的性能分析器,常常基于系统提供的“perf”指令,它就像是一个调试器,在合适的时候暂停进程,读取此时所有的线程和有关信息,从间隔的采样过程记录,从而得到运行性能输出。这种采样不需要重新添加和编译代码,但较可能漏掉时间短的函数。合理使用侵入式代码分析器,可以精细记录运行性能的细节,也能更少地影响待测程序的运行性能。

    “火旋风”分析器已经在GitHub上开源,并配有丰富的使用文档。

    项目主页: https://github.com/That3Percent/firestorm

    《数学读着读着想念Rust了,于是我做了个巨简单的光线追踪器》

    文章作者弗拉迪斯拉夫·奥列斯克是白俄罗斯国立大学数学系的大一新生,为了实验他学到的新知识,用Rust编写了这个光线追踪器例子。除了极其凡尔赛的文章标题,从光线、平面的计算到投影三维几何体,项目里的干货也十分足料。作者在文章的结尾,呈现了几个典型的几何体渲染例子,也大胆分享了编写过程中遇到的问题。

    贴文地址: https://www.reddit.com/r/rust/comments/lkg5w8/missed_rust_when_studying_maths_so_i_made_a_super/

    rust-analyzer发布第六十四期更新公告

    广泛使用的代码动态分析器rust-analyzer发布了此次公告。现在,编辑器能在语句块内部给定特定的提示。这将为更多的提示提供可能性:比如在match语句块中,根据枚举变量的可能性,提示还需要的解构语句臂。另外,编辑器将帮助生成获取-设置语法的函数,以便外界封装访问结构体部分变量的引用和可变引用。以自我类型“Self”指定的枚举变量,现在也将获得match解构有关的语法提示。此外,搜索功能现在支持搜索类型的所有构造函数。

    此次更新处理的问题包括for关键字的补全、dyn T类型的函数名补全等等,还包括若干项问题修复和内部性能提升。

    公告地址: https://rust-analyzer.github.io/thisweek/2021/02/15/changelog-64.html

    IntelliJ Rust发布第一百四十一期更新公告

    知名的代码编辑器在官方博客发布了此次更新公告。现在,插件已经支持最新2021.1预览版的编辑器软件。一些功能更新包括快速移除多余的函数参数,检测更多的编译错误,使用LLDB调试器帮助调试,新建函数的功能现在也支持异步函数。性能提升包括优化大模块的名称解析等等。本次更新也包括大量的小问题修复和内部性能提升。

    官方博客: https://intellij-rust.github.io/2021/02/15/changelog-141.html


    来自 日报小组 洛佳

    社区学习交流平台订阅:

    • Rustcc论坛: 支持rss

    • 微信公众号:Rust语言中文社区

    展开全文
  • 本文将基于 Roslyn 开发一个 C# 代码分析器,你不止可以将分析器作为 Visual Studio 代码分析和重构插件发布,还可以作为 NuGet 包发布。不管哪一种,都可以让我们编写的 C# 代码分析器工作起来并真正起到代码建议和...

    Roslyn 是 .NET 平台下十分强大的编译器,其提供的 API 也非常丰富好用。本文将基于 Roslyn 开发一个 C# 代码分析器,你不止可以将分析器作为 Visual Studio 代码分析和重构插件发布,还可以作为 NuGet 包发布。不管哪一种,都可以让我们编写的 C# 代码分析器工作起来并真正起到代码建议和重构的作用。


    本文将教大家如何从零开始开发一个基于 Roslyn 的 C# 源代码分析器 Analyzer 和修改器 CodeFixProvider。可以作为 Visual Studio 插件安装和使用,也可以作为 NuGet 包安装到项目中使用(无需安装插件)。无论哪一种,你都可以在支持 Roslyn 分析器扩展的 IDE(如 Visual Studio)中获得如下面动图所展示的效果。

    本文教大家可以做到的效果

    开发准备

    安装 Visual Studio 扩展开发工作负载

    你需要先安装 Visual Studio 的扩展开发工作负载,如果你还没有安装,那么请先阅读以下博客安装:

    Visual Studio 扩展开发

    创建一个分析器项目

    启动 Visual Studio,新建项目,然后在项目模板中找到 “Analyzer with Code Fix (.NET Standard)”,下一步。

    Analyzer with Code Fix 模板

    随后,取好项目名字之后,点击“创建”,你将来到 Visual Studio 的主界面。

    我为项目取的名称是 Walterlv.Demo.Analyzers,接下来都将以此名称作为示例。你如果使用了别的名称,建议你自己找到名称的对应关系。

    在创建完项目之后,你可选可以更新一下项目的 .NET Standard 版本(默认是 1.3,建议更新为 2.0)以及几个 NuGet 包。

    首次调试

    如果你现在按下 F5,那么将会启动一个 Visual Studio 的实验实例用于调试。

    Visual Studio 实验实例

    由于我们是一个分析器项目,所以我们需要在第一次启动实验实例的时候新建一个专门用来测试的小型项目。

    简单起见,我新建一个 .NET Core 控制台项目。新建的项目如下:

    测试用的控制台项目

    我们目前只是基于模板创建了一个分析器,而模板中自带的分析器功能是 “只要类型名称中有任何一个字符是小写的,就给出建议将其改为全部大写”。

    于是我们看到 Program 类名底下标了绿色的波浪线,我们将光标定位到 Program 类名上,可以看到出现了一个 “小灯泡” 提示。按下重构快捷键(默认是 Ctrl + .)后可以发现,我们的分析器项目提供的 “Make uppercase” 建议显示了出来。于是我们可以快速地将类名修改为全部大写。

    模板中自带的分析器建议

    因为我们在前面安装了 Visual Studio 扩展开发的工作负载,所以可以在 “视图”->“其他窗口” 中找到并打开 Syntax Visualizer 窗格。现在,请将它打开,因为接下来我们的代码分析会用得到这个窗格。

    打开语法可视化窗格

    如果体验完毕,可以关闭 Visual Studio;当然也可以在我们的分析器项目中 Shift + F5 强制结束调试。

    下次调试的时候,我们不需要再次新建项目了,因为我们刚刚新建的项目还在我们新建的文件夹下。下次调试只要像下面那样再次打开这个项目测试就好了。

    打开历史记录中的项目

    解读模板自带的分析器项目

    项目和解决方案

    在创建完项目之后,你会发现解决方案中有三个项目:

    Visual Studio 分析器解决方案

    • Walterlv.Demo.Analyzers
      • 分析器主项目,我们接下来分析器的主要逻辑代码都在这个项目中
      • 这个项目在编译成功之后会生成一个 NuGet 包,安装了此包的项目将会运行我们的分析器
    • Walterlv.Demo.Analyzers.Vsix
      • Visual Studio 扩展项目,我们会在这里 Visual Studio 插件相关的信息
      • 这个项目在便已成功之后会生成一个 Visual Studio 插件安装包,Visual Studio 安装了此插件后将会对所有正在编辑的项目运行我们的分析器
      • 这个项目在默认情况下是启动项目(按下 F5 会启动这个项目调试),调试时会启动一个 Visual Studio 的实验实例
    • Walterlv.Demo.Analyzers.Test
      • 单元测试项目
      • 模板为我们生成了比较多的辅助代码帮助我们快速编写用于测试我们分析器可用性的单元测试,我们接下来的代码质量也靠这个来保证

    在项目内部:

    • WalterlvDemoAnalyzersAnalyzer.cs
      • 模板中自带的分析器(Analyzer)的主要代码
      • 我们什么都还没有写的时候,里面已经包含一份示例用的分析器,其功能是找到包含小写的类名。
    • WalterlvDemoAnalyzersCodeFixProvider.cs
      • 模板中自带的代码修改器(CodeFixProvider)的主要代码
      • 我们什么都还没有写的时候,里面已经包含一份示例用的代码修改器,根据前面分析器中找到的诊断信息,给出修改建议,即只要类型名称中有任何一个字符是小写的,就给出建议将其改为全部大写
    • Resources.resx
      • 这里包含分析器建议使用的多语言信息

    多语言资源文件

    分析器代码(Analyzer)

    别看我们分析器文件中的代码很长,但实际上关键的信息并不多。

    我们现在还没有自行修改 WalterlvDemoAnalyzersAnalyzer 类中的任何内容,而到目前位置这个类里面包含的最关键代码我提取出来之后是下面这些。为了避免你吐槽这些代码编译不通过,我将一部分的实现替换成了 NotImplementedException

    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class WalterlvDemoAnalyzersAnalyzer : DiagnosticAnalyzer
    {
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            => throw new NotImplementedException();
    
        public override void Initialize(AnalysisContext context)
            => throw new NotImplementedException();
    }
    

    最关键的点:

    1. [DiagnosticAnalyzer(LanguageNames.CSharp)]
      • 为 C# 语言提供诊断分析器
    2. override SupportedDiagnostics
      • 返回此分析器支持的诊断规则
    3. override Initialize
      • 在此分析器初始化的时候执行某些代码

    现在我们分别细化这些关键代码。为了简化理解,我将多语言全部替换成了实际的字符串值。

    重写 SupportedDiagnostics 的部分,创建并返回了一个 DiagnosticDescriptor 类型的只读集合。目前只有一个 DiagnosticDescriptor,名字是 Rule,构造它的时候传入了一大堆字符串,包括分析器 Id、标题、消息提示、类型、级别、默认开启、描述信息。

    可以很容易看出,如果我们这个分析器带有多个诊断建议,那么在只读集合中返回多个 DiagnosticDescriptor 的实例。

    public const string DiagnosticId = "WalterlvDemoAnalyzers";
    
    private static readonly LocalizableString Title = "Type name contains lowercase letters";
    private static readonly LocalizableString MessageFormat = "Type name '{0}' contains lowercase letters";
    private static readonly LocalizableString Description = "Type names should be all uppercase.";
    private const string Category = "Naming";
    
    private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
    
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
    

    重写 Initialize 的部分,模板中注册了一个类名分析器,其实就是下面那个静态方法 AnalyzeSymbol

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
    }
    
    private static void AnalyzeSymbol(SymbolAnalysisContext context)
    {
        // 省略实现。
        // 在模板自带的实现中,这里判断类名是否包含小写字母,如果包含则创建一个新的诊断建议以改为大写字母。
    }
    

    代码修改器(CodeFixProvider)

    代码修改器文件中的代码更长,但关键信息也没有增加多少。

    我们现在也没有自行修改 WalterlvDemoAnalyzersCodeFixProvider 类中的任何内容,而到目前位置这个类里面包含的最关键代码我提取出来之后是下面这些。为了避免你吐槽这些代码编译不通过,我将一部分的实现替换成了 NotImplementedException

    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WalterlvDemoAnalyzersCodeFixProvider)), Shared]
    public class WalterlvDemoAnalyzersCodeFixProvider : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds
            => throw new NotImplementedException();
    
        public sealed override FixAllProvider GetFixAllProvider()
            => WellKnownFixAllProviders.BatchFixer;
    
        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
            => throw new NotImplementedException();
    }
    

    最关键的点:

    1. [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WalterlvDemoAnalyzersCodeFixProvider)), Shared]
      • 为 C# 语言提供代码修改器
    2. override FixableDiagnosticIds
      • 注意到前面 WalterlvDemoAnalyzersAnalyzer 类型中有一个公共字段 DiagnosticId 吗?在这里返回,可以为那里分析器找到的代码提供修改建议
    3. override GetFixAllProvider
      • 在最简单的示例中,我们将仅仅返回 BatchFixer,其他种类的 FixAllProvider 我将通过其他博客进行说明
    4. override RegisterCodeFixesAsync
      • FixableDiagnosticIds 属性中我们返回的那些诊断建议这个方法中可以拿到,于是为每一个返回的诊断建议注册一个代码修改器(CodeFix)

    在这个模板提供的例子中,FixableDiagnosticIds 返回了 WalterlvDemoAnalyzersAnalyzer 类中的公共字段 DiagnosticId

    public sealed override ImmutableArray<string> FixableDiagnosticIds =>
        ImmutableArray.Create(WalterlvDemoAnalyzersAnalyzer.DiagnosticId);
    

    RegisterCodeFixesAsync 中找到我们在 WalterlvDemoAnalyzersAnalyzer 类中找到的一个 Diagnostic,然后对这个 Diagnostic 注册一个代码修改(CodeFix)。

    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
    
        // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
        var diagnostic = context.Diagnostics.First();
        var diagnosticSpan = diagnostic.Location.SourceSpan;
    
        // Find the type declaration identified by the diagnostic.
        var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();
    
        // Register a code action that will invoke the fix.
        context.RegisterCodeFix(
            CodeAction.Create(
                title: title,
                createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c),
                equivalenceKey: title),
            diagnostic);
    }
    
    private async Task<Solution> MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
    {
        // 省略实现。
        // 将类名改为全大写,然后返回解决方案。
    }
    

    开发自己的分析器(Analyzer)

    一个简单的目标

    作为示例,我们写一个属性转换分析器,将自动属性转换为可通知属性。

    就是像以下上面的一种属性转换成下面的一种:

    public string Foo { get; set; }
    
    private string _foo;
    
    public string Foo
    {
        get => _foo;
        set => SetValue(ref _foo, value);
    }
    

    这里我们写了一个 SetValue 方法,有没有这个 SetValue 方法存在对我们后面写的分析器其实没有任何影响。不过你如果强迫症,可以看本文最后的“一些补充”章节,把 SetValue 方法加进来。

    开始添加最基础的代码

    于是,我们将 Initialize 方法中的内容改成我们期望的分析自动属性的语法节点分析。

    public override void Initialize(AnalysisContext context)
        => context.RegisterSyntaxNodeAction(AnalyzeAutoProperty, SyntaxKind.PropertyDeclaration);
    
    private void AnalyzeAutoProperty(SyntaxNodeAnalysisContext context)
    {
        // 你可以在这一行打上一个断点,这样你可以观察 `context` 参数。
    }
    

    上面的 AnalyzeAutoProperty 只是我们随便取的名字,而 SyntaxKind.PropertyDeclaration 是靠智能感知提示帮我找到的。

    现在我们来试着分析一个自动属性。

    按下 F5 调试,在新的调试的 Visual Studio 实验实例中,我们将鼠标光标放在 public string Foo { get; set; } 行上。如果我们提前在 AnalyzeAutoProperty 方法中打了断点,那么我们可以在此时观察到 context 参数。

    context 参数

    • CancellationToken 指示当前是否已取消分析
    • Node 语法节点
    • SemanticModel
    • ContainingSymbol 语义分析节点
    • Compilation
    • Options

    其中,Node.KindText 属性的值为 PropertyDeclaration。还记得前面让你先提前打开 Syntax Visualizer 窗格吗?是的,我们可以在这个窗格中找到 PropertyDeclaration 节点。

    我们可以借助这个语法可视化窗格,找到 PropertyDeclaration 的子节点。当我们一级一级分析其子节点的语法的时候,便可以取得这个语法节点的全部所需信息(可见性、属性类型、属性名称),也就是具备生成可通知属性的全部信息了。

    在语法可视化窗格中分析属性

    添加分析自动属性的代码

    由于我们在前面 Initialize 方法中注册了仅在属性声明语法节点的时候才会执行 AnalyzeAutoProperty 方法,所以我们在这里可以简单的开始报告一个代码分析 Diagnostic

    var propertyNode = (PropertyDeclarationSyntax)context.Node;
    var diagnostic = Diagnostic.Create(_rule, propertyNode.GetLocation());
    context.ReportDiagnostic(diagnostic);
    

    现在,WalterlvDemoAnalyzersAnalyzer 类的完整代码如下:

    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class WalterlvDemoAnalyzersAnalyzer : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "WalterlvDemoAnalyzers";
    
        private static readonly LocalizableString _title = "自动属性";
        private static readonly LocalizableString _messageFormat = "这是一个自动属性";
        private static readonly LocalizableString _description = "可以转换为可通知属性。";
        private const string _category = "Usage";
    
        private static readonly DiagnosticDescriptor _rule = new DiagnosticDescriptor(
            DiagnosticId, _title, _messageFormat, _category, DiagnosticSeverity.Info,
            isEnabledByDefault: true, description: _description);
    
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);
    
        public override void Initialize(AnalysisContext context) =>
            context.RegisterSyntaxNodeAction(AnalyzeAutoProperty, SyntaxKind.PropertyDeclaration);
    
        private void AnalyzeAutoProperty(SyntaxNodeAnalysisContext context)
        {
            var propertyNode = (PropertyDeclarationSyntax)context.Node;
            var diagnostic = Diagnostic.Create(_rule, propertyNode.GetLocation());
            context.ReportDiagnostic(diagnostic);
        }
    }
    

    可以发现代码并不多,现在运行,可以在光标落在属性声明的行时看到修改建议。如下图所示:

    在属性上有修改建议

    你可能会觉得有些不满,看起来似乎只有我们写的那些标题和描述在工作。但实际上你还应该注意到这些:

    1. DiagnosticId_messageFormat_description 已经工作起来了;
    2. 只有光标在属性声明的语句块时,这个提示才会出现,因此说明我们的已经找到了正确的代码块了;
    3. 不要忘了我们还有个 CodeFixProvider 没有写呢,你现在看到的依然还在修改大小写的部分代码是那个类(WalterlvDemoAnalyzersCodeFixProvider)里的。

    开发自己的代码修改器(CodeFixProvider)

    现在,我们开始进行代码修改,将 WalterlvDemoAnalyzersCodeFixProvider 类改成我们希望的将属性修改为可通知属性的代码。

    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WalterlvDemoAnalyzersCodeFixProvider)), Shared]
    public class WalterlvDemoAnalyzersCodeFixProvider : CodeFixProvider
    {
        private const string _title = "转换为可通知属性";
    
        public sealed override ImmutableArray<string> FixableDiagnosticIds =>
            ImmutableArray.Create(WalterlvDemoAnalyzersAnalyzer.DiagnosticId);
    
        public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
    
        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
            var diagnostic = context.Diagnostics.First();
            var declaration = (PropertyDeclarationSyntax)root.FindNode(diagnostic.Location.SourceSpan);
    
            context.RegisterCodeFix(
                CodeAction.Create(
                    title: _title,
                    createChangedSolution: ct => ConvertToNotificationProperty(context.Document, declaration, ct),
                    equivalenceKey: _title),
                diagnostic);
        }
    
        private async Task<Solution> ConvertToNotificationProperty(Document document,
            PropertyDeclarationSyntax propertyDeclarationSyntax, CancellationToken cancellationToken)
        {
            // 获取文档根语法节点。
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
    
            // 生成可通知属性的语法节点集合。
            var type = propertyDeclarationSyntax.Type;
            var propertyName = propertyDeclarationSyntax.Identifier.ValueText;
            var fieldName = $"_{char.ToLower(propertyName[0])}{propertyName.Substring(1)}";
            var newNodes = CreateNotificationProperty(type, propertyName, fieldName);
    
            // 将可通知属性的语法节点插入到原文档中形成一份中间文档。
            var intermediateRoot = root
                .InsertNodesAfter(
                    root.FindNode(propertyDeclarationSyntax.Span),
                    newNodes);
    
            // 将中间文档中的自动属性移除形成一份最终文档。
            var newRoot = intermediateRoot
                .RemoveNode(intermediateRoot.FindNode(propertyDeclarationSyntax.Span), SyntaxRemoveOptions.KeepNoTrivia);
    
            // 将原来解决方案中的此份文档换成新文档以形成新的解决方案。
            return document.Project.Solution.WithDocumentSyntaxRoot(document.Id, newRoot);
        }
    
        private async Task<Solution> ConvertToNotificationProperty(Document document,
            PropertyDeclarationSyntax propertyDeclarationSyntax, CancellationToken cancellationToken)
        {
            // 这个类型暂时留空,因为这是真正的使用 Roslyn 生成语法节点的代码,虽然只会写一句话,但相当长。
        }
    }
    

    还记得我们在前面解读 WalterlvDemoAnalyzersCodeFixProvider 类型时的那些描述吗?我们现在为一个诊断 Diagnostic 注册了一个代码修改(CodeFix),并且其回调函数是 ConvertToNotificationProperty。这是我们自己编写的一个方法。

    我在这个方法里面写的代码并不复杂,是获取原来的属性里的类型信息和属性名,然后修改文档,将新的文档返回。

    其中,我留了一个 CreateNotificationProperty 方法为空,因为这是真正的使用 Roslyn 生成语法节点的代码,虽然只会写一句话,但相当长。

    于是我将这个方法单独写在了下面。将这两个部分拼起来(用下面方法替换上面同名的方法),你就能得到一个完整的 WalterlvDemoAnalyzersCodeFixProvider 类的代码了。

    private SyntaxNode[] CreateNotificationProperty(TypeSyntax type, string propertyName, string fieldName)
        => new SyntaxNode[]
        {
            SyntaxFactory.FieldDeclaration(
                new SyntaxList<AttributeListSyntax>(),
                new SyntaxTokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)),
                SyntaxFactory.VariableDeclaration(
                    type,
                    SyntaxFactory.SeparatedList(new []
                    {
                        SyntaxFactory.VariableDeclarator(
                            SyntaxFactory.Identifier(fieldName)
                        )
                    })
                ),
                SyntaxFactory.Token(SyntaxKind.SemicolonToken)
            ),
            SyntaxFactory.PropertyDeclaration(
                type,
                SyntaxFactory.Identifier(propertyName)
            )
            .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
            .AddAccessorListAccessors(
                SyntaxFactory.AccessorDeclaration(
                    SyntaxKind.GetAccessorDeclaration
                )
                .WithExpressionBody(
                    SyntaxFactory.ArrowExpressionClause(
                        SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken),
                        SyntaxFactory.IdentifierName(fieldName)
                    )
                )
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                SyntaxFactory.AccessorDeclaration(
                    SyntaxKind.SetAccessorDeclaration
                )
                .WithExpressionBody(
                    SyntaxFactory.ArrowExpressionClause(
                        SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken),
                        SyntaxFactory.InvocationExpression(
                            SyntaxFactory.IdentifierName("SetValue"),
                            SyntaxFactory.ArgumentList(
                                SyntaxFactory.Token(SyntaxKind.OpenParenToken),
                                SyntaxFactory.SeparatedList(new []
                                {
                                    SyntaxFactory.Argument(
                                        SyntaxFactory.IdentifierName(fieldName)
                                    )
                                    .WithRefKindKeyword(
                                        SyntaxFactory.Token(SyntaxKind.RefKeyword)
                                    ),
                                    SyntaxFactory.Argument(
                                        SyntaxFactory.IdentifierName("value")
                                    ),
                                }),
                                SyntaxFactory.Token(SyntaxKind.CloseParenToken)
                            )
                        )
                    )
                )
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
            ),
        };
    

    实际上本文并不会重点介绍如何使用 Roslyn 生成新的语法节点,因此我不会解释上面我是如何写出这样的语法节点来的,但如果你对照着语法可视化窗格(Syntax Visualizer)来看的话,也是不难理解为什么我会这么写的。

    在此类型完善之后,我们再 F5 启动调试,可以发现我们已经可以完成一个自动属性的修改了,可以按照预期改成一个可通知属性。

    你可以再看看下面的动图:

    可以修改属性

    发布

    发布成 NuGet 包

    前往我们分析器主项目 Walterlv.Demo.Analyzers 项目的输出目录,因为本文没有改输出路径,所以在项目的 bin\Debug 文件夹下。我们可以找到每次编译产生的 NuGet 包。

    已经打出来的 NuGet 包

    如果你不知道如何将此 NuGet 包发布到 nuget.org,请在文本中回复,也许我需要再写一篇博客讲解如何推送。

    发布到 Visual Studio 插件商店

    前往我们分析器的 Visual Studio 插件项目 Walterlv.Demo.Analyzers.Vsix 项目的输出目录,因为本文没有改输出路径,所以在项目的 bin\Debug 文件夹下。我们可以找到每次编译产生的 Visual Studio 插件安装包。

    已经打出来的 Visual Studio 插件

    如果你不知道如何将此 Visual Studio 插件发布到 Visual Studio Marketplace,请在文本中回复,也许我需要再写一篇博客讲解如何推送。

    一些补充

    辅助源代码

    前面我们提到了 SetValue 这个方法,这是为了写一个可通知对象。为了拥有这个方法,请在我们的测试项目中添加下面这两个文件:

    一个可通知类文件 NotificationObject.cs:

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    
    namespace Walterlv.TestForAnalyzer
    {
        public class NotificationObject : INotifyPropertyChanged
        {
            protected bool SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
            {
                if (Equals(field, value))
                {
                    return false;
                }
    
                field = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                return true;
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
        }
    }
    

    一个用于分析器测试的类 Demo.cs:

    namespace Walterlv.TestForAnalyzer
    {
        class Demo : NotificationObject
        {
            public string Foo { get; set; }
        }
    }
    

    示例代码仓库

    代码仓库在我的 Demo 项目中,注意协议是 996.ICU 哟!

    别忘了单元测试

    别忘了我们一开始创建仓库的时候有一个单元测试项目,而我们全文都没有讨论如何充分利用其中的单元测试。我将在其他的博客中说明如何编写和使用分析器项目的单元测试。


    参考资料


    我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

    如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

    知识共享许可协议

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

    展开全文
  • 使用静态代码分析器有助于提升固件和捕获编译器难以察觉的问题。以下是每一位嵌入式软件开发工程师都应该熟悉的静态代码编译器的七种用法。 标准的C语言编译器在检查语法错误方面做得很好,并且能将其编译成可执行的...
  • erlang代码分析器dialyzer

    千次阅读 2013-06-14 09:11:28
    1、分析代码之前,要生成.dialyzer_plt文件,生成过程使用一个批处理文件 set HOME=e:/ dialyzer.exe --bulid_plt -r "C:\Program Files (x86)\erl5.9\lib\kernel-2.15\ebin" "C:\Program Files (x86)\erl5.9\lib\...
  • 语法分析主要用库解决了,代码变成了一棵树,但是变量类型,方法签名之类的东东都不清楚。 如果做IDE插件,可以享受IDE的语义分析能力。为了让程序独立运行,我自己按需实现了语义分析。 比较复杂,就分享一种最典型...
  • OACR Microsoft Auto Code Review 自动代码分析器
  • Java开源 代码分析器

    千次阅读 2010-06-20 16:36:00
    FindBugs 点击次数:260 FindBugs 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。利用这个工具,就可以在不实际运行程序的情况对软件进行分析。它可以帮助改进代码...
  • dialyzer:erlang代码分析器

    千次阅读 2012-12-21 14:45:45
    Erlang是一种“动态”语言,这会带来一个问题,单元测试不足以证明我写的代码是否足够正确。很难发现动态语言类型错用的问题。静态类型语言倒是很容易找到此类错误,但是Erlang是“动态的”。例如,length/1函数只能...
  • Java代码分析器(一): JDT入门

    千次阅读 2016-03-15 20:33:36
    抽象语法树是对程序代码的结构化表示,是对代码进行词法分析、语法分析后得到的产物。编译器要用到它,很多生产力工具也要用它,例如: IDE可以自动重构、自动生成一些代码、自动对不规范代码发出警告。这是很强很...
  • dialyzer: Erlang代码分析器

    千次阅读 2011-06-10 18:10:00
    Erlang是一种“动态”语言,这会带来一个问题,单元测试不足以证明我写的代码是否足够正确。很难发现动态语言类型错用的问题。静态类型语言倒是很容易找到此类错误,但是Erlang是“动态的”。例如,length/1函数只能...
  • 最近突发奇想,希望能写一个通用的代码分析工具(有点言过其实了,其实是针对C代码的)。这几天看代码看的我头晕眼花,虽然有Source Insight的帮助,仍然觉得很多地方不够智能。现在主要遇到的问题有以下几个: 1,...
  • 给editPlus增加一个PHP的代码分析器ZDE

    千次阅读 2004-12-05 10:44:00
    ZDE自带了一个代码分析器,很好用,我们也可以把它放到EP里,照我图里的设置ZendCodeAnalyzer我稍后回帖公布下载地址注意,输出选默认,而不是正则表达式上面的图片中,“命令栏”就填ZendCodeAnalyzer.exe路径下载...
  • C# 代码分析器 1.0 版开发完成

    千次阅读 2008-12-15 22:43:00
    功能:分析出类文件中,函数的调用层次。现在还是一个简单版本,只能够分析出类文件中函数的调用层次,不能够分析出属性的调用层次。... 使用方法: 将要分析代码粘贴到1#区域,点击Parse Fun按
  • 当我们用上篇博客的方式读入并解析Java代码时,JDT只会做词法分析和语法分析,而不会做语义分析,因此只能发现符号不合法、语句不完整、括号不匹配之类的简单错误,至于变量未声明、类型不匹配、缺少import、方法未...
  • 语法分析器代码 C语言

    热门讨论 2011-08-18 22:15:18
    语法分析器代码 C语言语法分析器代码 C语言语法分析器代码 C语言语法分析器代码 C语言语法分析器代码 C语言语法分析器代码 C语言
  • 随着 Roslyn 的发布,带来了越来越多更强大的代码分析器,可以为编写高质量的代码带来更多的帮助。 作为 .NET/C# 开发者,强烈建议安装本文推荐的几款代码分析器。 本文内容推荐类型简介Visual Studio 2019 自带的...
  • 我记得前面是说过词法分析器这个东西的,其作用也很直观,就是将Java源文件的字符流转换成对应的Token流,我们我们所谓规范化代码程序,让其将人能看懂的转换成机器能看懂的第一步。第二部到了语法分析器的时候,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,096
精华内容 8,038
关键字:

代码分析器