背景
最近试用ChatGPT给我带来了新的想法,如果你觉得我后面写的比较离谱,那确实有可能是我错误的高估了现有AI的能力,就当我是在做梦吧hhhh。总的来说这种对话的方式极大的降低了人机交互的门槛。相比于传统的交互方式,以自然语言和计算机进行有效的交互是听上去理所当然,但实际上一直都不太成熟的方式。而ChatGPT给我们带来了新的希望,我认为之后这个技术足够成熟之后将会改变很多现有的交互逻辑。可以设想,假如以后AI能正确理解绝大部分的自然语言,那么是不是很多任务都不需要用鼠标和键盘来操作了?而根据网友给出的参考资料,有人正在尝试将自然语言转化为一系列的API调用(参考最后的附录),这意味着AI生成的内容将具有可执行的特性,不同的API完全可以代替现有的人工操作,完成一场人机交互的变革。
继上一次我根据AST上下文来做可控的LiveTemplate之后,我继续尝试了提取代码的AST节点并结构化其中的语义信息配合可视化来提高日常工作效率。但短期内无论从安全角度还是对成果负责的角度来说,程序生成的结果,或者说AI的动作都必须经过人工的确认以避免AI失控造成不好的后果。也就是说在相当长的一段时间内,AI的操作必须要可观测、可干预。以此为前提让我有了一个科幻的想法:我对电脑用自然语言下达任务后,电脑在我的监视下逐步完成任务。选择离我最近的场景来说就是日常的编码工作在有详细设计的情况下是否有希望完全自动化?
可行性分析
现有的代码生成AI大多是基于工程内已有的文件、注释、当前文件光标的上下文来猜测并生成代码,但这里面缺少了一个重要的信息就是目的。AI无法准确猜测人的意图,比如完成某个API的编码通常会分成接入层、业务层、数据层,多层次+多个方法的组合。AI每次只能预测我选定光标位置的代码内容,而无法连续跳跃多个层次来完成这个任务,依然需要人工大量操作IDE。如果能以自然语言提供给AI这个目的,是否有可能让AI自行完成这一系列的动作呢?由此会带来的一系列问题。开发人员对IDE的操作是否能通过文本精确的描述?开发人员操作IDE时依赖的信息有没有可能被收集起来并打上标记?如果将我日常操作IDE的记录和操作IDE时候的信息完整的记录下来并打上标记,能否训练出一个AI来理解并代替我进行IDE的操作?
以我目前的AI水平还停留在手写数字识别的入门级别,虽然有计划学习相关知识,不过并不是抱很大的希望能独立搞定模型的设计、训练之类如此专业的任务。但以我目前的能力,处理点数据、集成已有的功能还是不成什么问题的。 因此我有一个长期计划的想法,设计并收集开发操作IDE的上下文信息,包括但不限于工程类型、文件组织结构、版本控制信息、产物信息、光标上下文信息等。依赖这些详细的信息,自动或手动的分析任务场景打上标记作为下一步AI训练的基础数据。抽取并实现一组API来获取、操作IDE内的动作并实时的反馈到屏幕上供人工干预这个过程。至于这个AI怎么训练,那可以将这个项目开源交给AI的专业人士搞定,我可以力所能及的收集数据或组合已有功能。退一步来说,就算AI搞不出来,收集的这些信息可以继续试用传统方式来处理,也能推动现一点点xxxAsCode的进程。
方案设计
项目名代号暂定为skykoma,分别取自skynet和tachikoma的一半,如果有好的名字欢迎反馈给我。整个项目拆分成若干模块分别独立实现,如果有生之年每个部分最后都能完全实现,则组合到一起。
信息收集模块:收集当前工程结构、版本控制、产物、光标上下文、IDE等信息,这些信息等价于开发员看到和了解到的信息。收集开发人员对IDE的操作序列数据。
信息处理模块:对收集到的信息做预处理,标记子序列对应的场景并用自然语言描述,在必要的位置插入人工干预的节点,以此作为训练的基础数据。
指令生成模块:接受自然语言描述的任务,生成一系列结构化指令
指令调度模块:接收一系列结构化指令并转化成具体的动作,动作的序列必须满足人工可观测、可干预、可理解,符合正常人操作的习惯,且动作序列有对应的API。理论上训练数据如果足够好可以达到部分效果,但为了可控和安全还是需要一个调度模块,为的是有机会通过程序强制干预AI的结果,加一道保险。
动作执行模块:给定具体动作并执行的同时收集结果、可视化反馈
以一个常见的场景为例:
自然语言描述:查找ClassA的所有实现类。
指令生成模块:打开搜索功能,输入关键字ClassA,从结果中定位ClassA,打开ClassA对应的文件编辑器,光标定位到ClassA的声明位置,搜索实现类(Ctrl+Alt+B)
指令调度模块插入必要指令:打开搜索功能,输入关键字ClassA,从结果中定位ClassA,判断是否能定位到ClassA(满足人工干预的要求),尝试打开ClassA对应的文件编辑器,光标定位到ClassA的声明位置,搜索实现类(Ctrl+Alt+B),收集搜索结果同时展示到屏幕上(满足可观测的要求)
指令调度模块转化指令到具体的API:打开搜索功能,输入关键字ClassA,从结果中定位ClassA,判断是否能定位到ClassA(满足人工干预的要求),尝试打开ClassA对应的文件编辑器,搜索当前文件下所有的class声明(指令调度需要生成API可支持的操作),寻找ClassA的声明位置(指令调度需要生成API可支持的操作),搜索实现类(Ctrl+Alt+B),收集搜索结果同时展示到屏幕上(满足可观测的要求)
执行模块:依赖指定调度的API结果控制IDE依次执行对应的指令
以下的ChatGPT给出的示例代码
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; public class FindClassImplementationsAction extends AnAction { @Override public void actionPerformed(@NotNull AnActionEvent e) { // 获取当前项目 Project project = e.getProject(); if (project == null) { return; } // 获取当前光标所在的类 PsiElement psiElement = e.getData(CommonDataKeys.PSI_ELEMENT); if (psiElement instanceof PsiClass) { PsiClass psiClass = (PsiClass) psiElement; // 查找所有实现该类的子类 List<PsiClass> implementations = getImplementations(psiClass); // 输出结果 StringBuilder sb = new StringBuilder(); sb.append("Implementations of ").append(psiClass.getName()).append(":\n\n"); for (PsiClass implementation : implementations) { sb.append(implementation.getQualifiedName()).append("\n"); } System.out.println(sb.toString()); } } private List<PsiClass> getImplementations(PsiClass psiClass) { List<PsiClass> implementations = new ArrayList<>(); Project project = psiClass.getProject(); GlobalSearchScope scope = GlobalSearchScope.projectScope(project); String classQualifiedName = psiClass.getQualifiedName(); // 查找实现该类或者接口的所有类 Query<PsiClass> query = ClassInheritorsSearch.search(psiClass, scope, true); for (PsiClass result : query) { if (result != null && StringUtil.isNotEmpty(result.getQualifiedName())) { if (!result.getQualifiedName().equals(classQualifiedName)) { implementations.add(result); } } } return implementations; } } |
概要设计
以上模块将会划分成多个开源项目,为了便于实现部分逻辑上的模块会放到同一个实际的项目中
交互层 skykoma-plugin-idea(1), skykoma-plugin-vscode(2), skykoma-chatbot(3) , skykoma-agent(待定), skykoma-workspace(待定) 顾名思义交互层定位于用户的输入和输出,同一管理最外层的交互
数据层 skykoma-data-collector(4), skykoma-data-handler(5), 收集和处理所有相关的数据并存储、上报
逻辑层 skykoma-core(6), skykoma-cmd-generator(7), skykoma-api-transformer(8), skykoma-api-worker(9), core就是核心的模型负责自然语言处理并生成第一版指令序列,cmd-generator负责指令的调度、插值、过滤以符合前文所说的各种条件,api-transformer负责把指令序列做翻译成现存工具的API调用, worker负责调用具体的API
执行层 skykoma-api: skykoma-api-idea,skykoma-api-vscode,skykoma-api-git(10),skykoma-api-mvn(11),skykoma-api-gitlab(12),skykoma-api-github(13), 顾名思义,实现整合各个现有工具的功能,以localhost HTTP、cmdline、rpc等方式对外提供服务
里程碑规划
v0.1 搭框架
手动完成realworld或者其他什么的工程编写作为测试项目,要求满足常见的企业级开发特性。
初始化idea插件工程(https://github.com/dev-assistant/skykoma-plugin-idea),支持私有化部署,收集idea当前的项目信息。
初始化数据收集工程(https://github.com/dev-assistant/skykoma-data-collector),支持私有化部署,数据存储位置可自行选择。
skykoma-plugin-idea概要设计
架构上因为不想出现多个idea插件,因此设计一个skykoma-api-idea负责和idea的api具体交互,kykoma-plugin-idea依赖skykoma-api-idea来获得对IDE完整的感知和控制能力。plugin支持常用的LiveTemplate,设计生成规则,在固定场景下支持人工触发生成。skykoma-api-idea收集idea的状态和操作事件并上报给skykoma-data-collector达成数据收集目的。同时也可以通过websocket、http等协议监听本地端口或从外部的控制模块获取指令。
模板代码自动生成功能
模板代码生成主要依赖于LiveTemplate实现,分为静态生成和动态生成,静态生成是上下文无关的,动态生成可感知代码上下文。
静态生成
如@Autowire @Column 之类的,试用idea自带API完成命名风格的转换。
动态生成
日志:寻找上下文中可用的LOGGER,若LOGGER不存在则尝试先生成LOGGER字段,并根据当前上下文插入LOGGER语句。
SQL:寻找上下文中的ORM注解信息,根据字段类型生成默认的建表语句。
数据复制:根据上下文获取指定类的所有属性,复制到当前上下文中或指定的对象中,代替动态的反射copy避免运行时异常。
Service:增强自带的创建类过程,根据类名猜测正确的包名,一键生成带注解的Service类和接口并实现对应接口满足主流项目的风格要求,减少人工操作。
代码见 https://github.com/dev-assistant/skykoma-plugin-idea
数据上报功能
TODO 设计数据结构表示上下文,包括当前窗口信息、窗口标题、项目信息、git信息、全量AST信息等
代码见 https://github.com/dev-assistant/skykoma-data-collector
skykoma-data-collector概要设计
负责skykoma需要的数据收集工作,用多种合适的存储方案持久化用户每个操作的上下文信息和事件。语义图暂定neo4j图数据库,其他结构化信息继续使用mysql。
设计数据结构存储相关信息
有了全量AST和语义,才有可能做到
1.给定任意项目任意API、自动提取可运行的最小依赖沙盒环境
2.任意复杂业务逻辑分析,不重不漏
3.沙盒环境的修改可回填到原始项目:可debug、做测试用例、分析逻辑、修改
4.因沙河内依赖最小化了,理论上可以用自然语言总结所有的上下文和代码意图信息、为炼丹做准备。退一步即使炼丹失败,也能快速抽象出大量模板代码来使用
5.反向给定自然语言生成沙盒环境、可完成对任意项目的patch
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
1.ProjectEntity (project:ProjectEntity { key:key//项目key name:name//项目名称 }) id 1 项目id key:key//项目key name:name//项目名称 name realworld 项目名称 CREATE INDEX idx_project_key IF NOT EXISTS FOR (m:ProjectEntity) ON (m.key) ; 2.ScanRecordEntity (scanRecord:ScanRecordEntity){ scanId:scanId//扫描id } CREATE INDEX idx_scan_record_scanId IF NOT EXISTS FOR (m:ScanRecordEntity) ON (m.scanId) ; //扫描信息所属关系 1:n (:ProjectEntity)-[:HAS_SCAN_RECORD]->(:ScanRecordEntity) 3.VCSEntity (vcsEntity:VCSEntity{ name:name,//仓库名称 path:path,//仓库本地绝对路径 vcsType:vcsType//仓库类型 git }) VCSEntity id 7aaf76a6-385a-480e-9377-335d798bb516 name realworld-mdd path D:\code\realworld-mdd vcsType git createdAt 1678551239850 updatedAt 1678551239850 //版本控制信息关系 1:1 (:ProjectEntity)-[:VCS_BY]->(:VCSEntity) 4.ModuleEntity (module:ModuleEntity { name:name//模块名称 }) ModuleEntity id 8a078585-5c06-43be-93b0-dd551244c64c name root 模块名称 createdAt 1678551238729 updatedAt 1678551239249 //模块包含关系 1:n (:ScanRecordEntity)-[:CONTAINS]->(:ModuleEntity) 5.FileEntity (file:FileEntity{ name:name,//文件名 type:type,//文件类型 file/folder relativePath:relativePath//相对路径 }) FileEntity id d9cbe0a4-3f60-4704-a419-7489ee530fc8 文件id name user type folder 文件类型 file/folder relativePath src-gen\main\java\cn\hylstudio\mdse\demo\realworld\model\response\user 相对路径 createdAt 1678551233924 updatedAt 1678551239353 CREATE INDEX idx_file_id IF NOT EXISTS FOR (m:FileEntity) ON (m.id) ; CREATE INDEX idx_file_relativePath IF NOT EXISTS FOR (m:FileEntity) ON (m.relativePath) ; //符号连接关系 n:1 (:FileEntity)-[:SYMBOL_LINK]->(:FileEntity) //项目根目录 1:1 (:ScanRecordEntity)-[:ROOT_AT]->(:FileEntity) //模块根目录 1:n (:ModuleEntity)-[:MODULE_ROOT{ type: type//src testSrc resources testResources }]->(:FileEntity) 6.PsiElementEntity (:PsiElementEntity{//语法树 className:className//psiElement实现类 startOffset //文本开始位置 endOffset//文本结束位置 originText//原始文本 }) PsiElementEntity id f30db139-4a90-4c10-b740-ac85514ab787 className com.intellij.psi.impl.source.PsiImportListImpl startOffset 59 endOffset 857 originText 'java code' createdAt 1680274076903 updatedAt 1680274076903 6.1.类 com.intellij.psi.impl.source.PsiClassImpl $.qualifiedName str //全限定类名 com.a.b.c.ClassA 6.2.注解 com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl $.qualifiedName str //全限定类名 com.a.b.c.AnnotationA $.attributes arr //属性数组 $.attributes[i] obj $.attributes[i].name str //属性名 $.attributes[i].values arr //属性值 $.attributes[i].values[i] str //属性值 //文件包含语法树根节点 1:n (:FileEntity)-[:HAS_PSI_ELEMENTS]->(:PsiElementEntity) //语法树包含关系 1:n (:PsiElementEntity)-[:CONTAINS]->(:PsiElementEntity) 7.AnnotationAttrEntity (:AnnotationAttrEntity{//注解属性 name values }) CREATE INDEX idx_psi_element_id IF NOT EXISTS FOR (m:PsiElementEntity) ON (m.id) ; CREATE INDEX idx_psi_className IF NOT EXISTS FOR (m:PsiElementEntity) ON (m.className) ; CREATE INDEX idx_psi_qualifiedName IF NOT EXISTS FOR (m:PsiElementEntity) ON (m.qualifiedName) ; //注解关系 1:n (:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiClassImpl'})-[:HAS_ANNOTATION]->(:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) (:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'})-[:HAS_ANNOTATION]->(:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) //注解和注解属性的关系1:n (:PsiElementEntity{className:"com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl"})-[:HAS_ATTR]->(:AnnotationAttrEntity) 8.ApiEndpointEntity (:ApiEndpointEntity{ scanId: scanId, method: httpMethod//RequestMethod.GET POST .... path: "/1/2/3" // /api/public/1/2/3 }) CREATE INDEX idx_api_endpoint_scanId IF NOT EXISTS FOR (m:ApiEndpointEntity) ON (m.scanId) ; CREATE INDEX idx_api_endpoint_path IF NOT EXISTS FOR (m:ApiEndpointEntity) ON (m.path) ; //api入口绑定关系 1:n (method:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'}) -[:HAS_ANNOTATION]-> (methodAnnotation:PsiElementEntity{ className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl', qualifiedName: 'org.springframework.web.bind.annotation.RequestMapping' }) (method:PsiElementEntity)-[:HAS_ENDPOINT]->(:ApiEndPointEntity) //查看IDEA日志 Get-Content .\build\idea-sandbox\system\log\idea.log -Tail 100 -Wait //列出所有项目 MATCH (project:ProjectEntity) RETURN project.key //列出指定项目下的扫描记录 MATCH (project:ProjectEntity)-[:HAS_SCAN_RECORD]->(scanRecord:ScanRecordEntity) WHERE project.key = "realworld-mdd" RETURN scanRecord.scanId, scanRecord.createdAt ORDER BY scanRecord.createdAt DESC //查看某次扫描记录 MATCH (project:ProjectEntity) -[:HAS_SCAN_RECORD]-> (scanRecord:ScanRecordEntity) -[*]- (related) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" RETURN project, scanRecord, related // 查找要连接ModuleRoot的FileEntity和FileEntity MATCH (scanRecord:ScanRecordEntity)-[rr:CONTAINS]->(module:ModuleEntity)-[rrr:MODULE_ROOT]->(moduleRoot:FileEntity) MATCH (scanRecord)-[:ROOT_AT]->(:FileEntity)-[:CONTAINS*1..]->(srcRoot:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND moduleRoot.relativePath = srcRoot.relativePath AND moduleRoot <> srcRoot RETURN scanRecord, rr, module, rrr, moduleRoot, srcRoot // 连接ModuleRoot的FileEntity和FileEntity MATCH (scanRecord:ScanRecordEntity)-[rr:CONTAINS]->(module:ModuleEntity)-[rrr:MODULE_ROOT]->(moduleRoot:FileEntity) MATCH (scanRecord)-[:ROOT_AT]->(:FileEntity)-[:CONTAINS*1..]->(srcRoot:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND moduleRoot.relativePath = srcRoot.relativePath AND moduleRoot <> srcRoot MERGE (moduleRoot)-[rrrr:SYMBOL_LINK]->(srcRoot) RETURN scanRecord, rr, module, rrr, moduleRoot, rrrr, srcRoot // 断开连接ModuleRoot的FileEntity和FileEntity MATCH (scanRecord:ScanRecordEntity)-[rr:CONTAINS]->(module:ModuleEntity)-[rrr:MODULE_ROOT]->(moduleRoot:FileEntity) MATCH (scanRecord)-[:ROOT_AT]->(:FileEntity)-[:CONTAINS*1..]->(srcRoot:FileEntity) MATCH (moduleRoot)-[link:SYMBOL_LINK]->(srcRoot:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND moduleRoot.relativePath = srcRoot.relativePath AND moduleRoot <> srcRoot DELETE link RETURN scanRecord, rr, module, rrr, moduleRoot, srcRoot // -[:TYPE*minHops..maxHops]-> //使用这个连接查询后面的文件树 MATCH (project:ProjectEntity) -[:HAS_SCAN_RECORD]->(scanRecord:ScanRecordEntity) -[:CONTAINS]->(module:ModuleEntity) -[:MODULE_ROOT]->(moduleRoot:FileEntity) -[:SYMBOL_LINK]->(srcRoot:FileEntity) -[:CONTAINS*1..]->(files) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" RETURN project, scanRecord, module, moduleRoot, srcRoot, files //查找指定扫描记录里的单个文件 MATCH (scanRecord:ScanRecordEntity)-[:ROOT_AT|CONTAINS*1..]->(file:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.relativePath = "src\\main\\java\\cn\\hylstudio\\mdse\\demo\\realworld\\controller\\user\\UserController.java" RETURN file //查看指定文件的语法树 MATCH (scanRecord:ScanRecordEntity)-[:ROOT_AT|CONTAINS*1..]->(file:FileEntity) -[:HAS_PSI_ELEMENTS|CONTAINS*1..]->(elements:PsiElementEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.relativePath = "src\\main\\java\\cn\\hylstudio\\mdse\\demo\\realworld\\controller\\user\\UserController.java" RETURN file,elements //获取当前文件所有的语法树节点类型 // RETURN distinct(psiElements.className) "com.intellij.psi.impl.source.tree.java.PsiPackageStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiKeywordImpl" "com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl" "com.intellij.psi.impl.source.tree.java.PsiJavaTokenImpl" "com.intellij.psi.impl.source.tree.java.PsiReferenceParameterListImpl" "com.intellij.psi.impl.source.tree.java.PsiIdentifierImpl" "com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl" "com.intellij.psi.impl.source.PsiImportListImpl" "com.intellij.psi.impl.source.PsiImportStatementImpl" "com.intellij.psi.impl.source.PsiClassImpl" "com.intellij.psi.impl.source.PsiMethodImpl" "com.intellij.psi.impl.source.PsiModifierListImpl" "com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl" "com.intellij.psi.impl.source.tree.java.PsiAnnotationParamListImpl" "com.intellij.psi.impl.source.tree.java.PsiNameValuePairImpl" "com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl" "com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl" "com.intellij.psi.impl.source.PsiTypeElementImpl" "com.intellij.psi.impl.source.PsiParameterListImpl" "com.intellij.psi.impl.source.PsiParameterImpl" "com.intellij.psi.impl.source.tree.java.PsiCodeBlockImpl" "com.intellij.psi.impl.source.tree.java.PsiReturnStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiMethodCallExpressionImpl" "com.intellij.psi.impl.source.tree.java.PsiExpressionListImpl" "com.intellij.psi.impl.source.tree.java.PsiExpressionStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiDeclarationStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiLocalVariableImpl" "com.intellij.psi.impl.source.tree.java.PsiIfStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiBlockStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiBinaryExpressionImpl" "com.intellij.psi.impl.source.tree.java.PsiTypeParameterListImpl" "com.intellij.psi.impl.source.PsiReferenceListImpl" "com.intellij.psi.impl.source.tree.PsiCommentImpl" "com.intellij.psi.impl.source.tree.java.PsiPrefixExpressionImpl" "com.intellij.psi.impl.source.tree.java.PsiSwitchStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiSwitchLabelStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiCaseLabelElementListImpl" "com.intellij.psi.impl.source.tree.java.PsiNewExpressionImpl" "com.intellij.psi.impl.source.PsiDiamondTypeElementImpl" "com.intellij.psi.impl.source.tree.java.PsiForeachStatementImpl" "com.intellij.psi.impl.source.tree.java.PsiClassObjectAccessExpressionImpl" "com.intellij.psi.impl.source.PsiFieldImpl" "com.intellij.psi.impl.source.tree.java.PsiArrayInitializerMemberValueImpl" "com.intellij.psi.impl.source.javadoc.PsiDocCommentImpl" "com.intellij.psi.impl.source.javadoc.PsiDocTokenImpl" //获取一个文件中的类、类的注解、类中的方法、方法注解,不使用HAS_ANNOTATION MATCH (scanRecord:ScanRecordEntity)-[:ROOT_AT|CONTAINS*1..]->(file:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.relativePath = "src\\main\\java\\cn\\hylstudio\\mdse\\demo\\realworld\\controller\\user\\UserController.java" OPTIONAL MATCH (file)-[:HAS_PSI_ELEMENTS|CONTAINS*1..]-> //match class info (class:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiClassImpl'}) OPTIONAL MATCH (class)-[:CONTAINS]-> (classModifierList:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiModifierListImpl'}) OPTIONAL MATCH (classModifierList)-[:CONTAINS]-> (classAnnotation:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) OPTIONAL MATCH (classAnnotation)-[:HAS_ATTR]-> (classAnnotationAttr:AnnotationAttrEntity) //match method info OPTIONAL MATCH (class)-[:CONTAINS]-> (method:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'}) OPTIONAL MATCH (method)-[:CONTAINS]-> (mehotdModifierList:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiModifierListImpl'}) OPTIONAL MATCH (mehotdModifierList)-[:CONTAINS]-> (methodAnnotation:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) OPTIONAL MATCH (methodAnnotation)-[:HAS_ATTR]-> (methodAnnotationAttr:AnnotationAttrEntity) return class,classModifierList,classAnnotation,classAnnotationAttr, method,mehotdModifierList,methodAnnotation,methodAnnotationAttr //连接所有class和method上的注解 MATCH (scanRecord:ScanRecordEntity)-[:MODULE_ROOT|SYMBOL_LINK|CONTAINS*1..]->(file:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.type = "file" MATCH (file)-[:HAS_PSI_ELEMENTS|CONTAINS*1..]-> //match class info (class:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiClassImpl'}) MATCH (class)-[:CONTAINS]-> (classModifierList:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiModifierListImpl'}) MATCH (classModifierList)-[:CONTAINS]-> (classAnnotation:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) MATCH (classAnnotation)-[:HAS_ATTR]-> (classAnnotationAttr:AnnotationAttrEntity) //match method info MATCH (class)-[:CONTAINS]-> (method:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'}) MATCH (method)-[:CONTAINS]-> (methodModifierList:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiModifierListImpl'}) MATCH (methodModifierList)-[:CONTAINS]-> (methodAnnotation:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) MATCH (methodAnnotation)-[:HAS_ATTR]-> (methodAnnotationAttr:AnnotationAttrEntity) MERGE (class)-[:HAS_ANNOTATION]->(classAnnotation) MERGE (method)-[:HAS_ANNOTATION]->(methodAnnotation) //获取一个文件中的类、类的注解、类中的方法、方法注解,使用HAS_ANNOTATION MATCH (scanRecord:ScanRecordEntity)-[:ROOT_AT|CONTAINS*1..]->(file:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.relativePath = "src\\main\\java\\cn\\hylstudio\\mdse\\demo\\realworld\\controller\\user\\UserController.java" OPTIONAL MATCH (file)-[:HAS_PSI_ELEMENTS|CONTAINS*1..]-> //match class info (class:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiClassImpl'}) OPTIONAL MATCH (class)-[:HAS_ANNOTATION]-> (classAnnotation:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) OPTIONAL MATCH (classAnnotation)-[:HAS_ATTR]-> (classAnnotationAttr:AnnotationAttrEntity) //match method info OPTIONAL MATCH (class)-[:CONTAINS]-> (method:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'}) OPTIONAL MATCH (method)-[:HAS_ANNOTATION]-> (methodAnnotation:PsiElementEntity{className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl'}) OPTIONAL MATCH (methodAnnotation)-[:HAS_ATTR]-> (methodAnnotationAttr:AnnotationAttrEntity) return class,classAnnotation,classAnnotationAttr, method,methodAnnotation,methodAnnotationAttr //单个文件,根据注解查询Api入口,并关联到方法上 MATCH (scanRecord:ScanRecordEntity)-[:ROOT_AT|CONTAINS*1..]->(file:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.relativePath = "src\\main\\java\\cn\\hylstudio\\mdse\\demo\\realworld\\controller\\user\\UserController.java" MATCH (file)-[:HAS_PSI_ELEMENTS|CONTAINS*1..]-> //match class info (class:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiClassImpl'}) OPTIONAL MATCH (class)-[:HAS_ANNOTATION]-> (classAnnotation:PsiElementEntity{ className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl', qualifiedName: 'org.springframework.web.bind.annotation.RequestMapping' }) MATCH (classAnnotation)-[:HAS_ATTR]-> (classAnnotationAttr:AnnotationAttrEntity{name:"value"}) //match method info MATCH (class)-[:CONTAINS]-> (method:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'}) MATCH (method)-[:HAS_ANNOTATION]-> (methodAnnotation:PsiElementEntity{ className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl', qualifiedName: 'org.springframework.web.bind.annotation.RequestMapping' }) MATCH (methodAnnotation)-[:HAS_ATTR]-> (apiPathsOnMethod:AnnotationAttrEntity{name:"value"}) MATCH (methodAnnotation)-[:HAS_ATTR]-> (apiMethods:AnnotationAttrEntity{name:"method"}) UNWIND classAnnotationAttr.values AS prefix UNWIND apiMethods.values AS httpMethod UNWIND apiPathsOnMethod.values AS path MERGE (method)-[:HAS_ENDPOINT]->(apiEndpoint:ApiEndpointEntity{method: httpMethod, path: prefix + path}) RETURN method, apiEndpoint //全量文件,根据注解生成Api入口,并关联到方法和扫描记录上 MATCH (scanRecord:ScanRecordEntity)-[:MODULE_ROOT|SYMBOL_LINK|CONTAINS*1..]->(file:FileEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" AND file.type = "file" MATCH (file)-[:HAS_PSI_ELEMENTS|CONTAINS*1..]-> //match class info (class:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiClassImpl'}) OPTIONAL MATCH (class)-[:HAS_ANNOTATION]-> (classAnnotation:PsiElementEntity{ className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl', qualifiedName: 'org.springframework.web.bind.annotation.RequestMapping' }) MATCH (classAnnotation)-[:HAS_ATTR]-> (classAnnotationAttr:AnnotationAttrEntity{name:"value"}) //match method info MATCH (class)-[:CONTAINS]-> (method:PsiElementEntity{className:'com.intellij.psi.impl.source.PsiMethodImpl'}) MATCH (method)-[:HAS_ANNOTATION]-> (methodAnnotation:PsiElementEntity{ className:'com.intellij.psi.impl.source.tree.java.PsiAnnotationImpl', qualifiedName: 'org.springframework.web.bind.annotation.RequestMapping' }) MATCH (methodAnnotation)-[:HAS_ATTR]-> (apiPathsOnMethod:AnnotationAttrEntity{name:"value"}) MATCH (methodAnnotation)-[:HAS_ATTR]-> (apiMethods:AnnotationAttrEntity{name:"method"}) UNWIND classAnnotationAttr.values AS prefix UNWIND apiMethods.values AS httpMethod UNWIND apiPathsOnMethod.values AS path MERGE (apiEndpoint:ApiEndpointEntity {scanId: scanRecord.scanId, method: httpMethod, path: prefix + path}) MERGE (method)-[:HAS_ENDPOINT]->(apiEndpoint) MERGE (scanRecord)-[:HAS_ENDPOINT]->(apiEndpoint) RETURN method, apiEndpoint //查找所有ApiEndpoint MATCH (scanRecord:ScanRecordEntity)-[:HAS_ENDPOINT]->(apiEndpoint:ApiEndpointEntity) WHERE scanRecord.scanId = "643e424c1b99436f82551420981669db" RETURN apiEndpoint |
参考
https://arxiv.org/pdf/2302.04761.pdf
https://arxiv.org/pdf/2211.10435.pdf
0 Comments