├── .eslintrc.json ├── .gitignore ├── .vs └── slnx.sqlite ├── .vscode └── launch.json ├── .vscodeignore ├── 3rd └── luacheck │ └── luacheck.exe ├── CHANGELOG.md ├── README.md ├── client ├── commands │ ├── busted.js │ ├── codemetrics-details.js │ └── ldoc.js ├── extension.js ├── lib │ ├── constants.js │ ├── logger.js │ ├── metrics │ │ ├── index.js │ │ ├── lib │ │ │ ├── analysis.js │ │ │ ├── utils.js │ │ │ └── walker.js │ │ └── syntax │ │ │ ├── AssignmentStatement.js │ │ │ ├── BinaryExpression.js │ │ │ ├── BooleanLiteral.js │ │ │ ├── BreakStatement.js │ │ │ ├── CallExpression.js │ │ │ ├── CallStatement.js │ │ │ ├── Chunk.js │ │ │ ├── DoStatement.js │ │ │ ├── ElseClause.js │ │ │ ├── ElseifClause.js │ │ │ ├── ForGenericStatement.js │ │ │ ├── ForNumericStatement.js │ │ │ ├── FunctionDeclaration.js │ │ │ ├── GotoStatement.js │ │ │ ├── Identifier.js │ │ │ ├── IfClause.js │ │ │ ├── IfStatement.js │ │ │ ├── IndexExpression.js │ │ │ ├── LabelStatement.js │ │ │ ├── LocalStatement.js │ │ │ ├── LogicalExpression.js │ │ │ ├── MemberExpression.js │ │ │ ├── NilLiteral.js │ │ │ ├── NumericLiteral.js │ │ │ ├── RepeatStatement.js │ │ │ ├── ReturnStatement.js │ │ │ ├── StringCallExpression.js │ │ │ ├── StringLiteral.js │ │ │ ├── TableCallExpression.js │ │ │ ├── TableConstructorExpression.js │ │ │ ├── TableKey.js │ │ │ ├── TableKeyString.js │ │ │ ├── UnaryExpression.js │ │ │ ├── VarargLiteral.js │ │ │ ├── WhileStatement.js │ │ │ ├── index.js │ │ │ └── tableValue.js │ ├── protocols.js │ └── utils.js └── providers │ └── codemetrics-provider.js ├── images ├── complete.gif ├── def-peak.gif ├── diagnostics.gif ├── format.gif ├── goto-def.gif ├── icon.png ├── ldoc.gif ├── metrics.gif ├── rename.gif ├── return-table.gif ├── signature.gif └── symbol-list.gif ├── jsconfig.json ├── package-lock.json ├── package.json ├── server ├── coder.js ├── lib │ ├── busted.js │ ├── engine │ │ ├── analysis.js │ │ ├── completion.js │ │ ├── core.js │ │ ├── definition.js │ │ ├── extend.js │ │ ├── index.js │ │ ├── is.js │ │ ├── linear-stack.js │ │ ├── luaenv.js │ │ ├── symbol.js │ │ ├── typeof.js │ │ └── utils.js │ └── symbol │ │ ├── scope.js │ │ ├── stack.js │ │ ├── symbol-traits.js │ │ ├── types │ │ ├── AssignmentStatement.js │ │ ├── BinaryExpression.js │ │ ├── CallExpression.js │ │ ├── CallStatement.js │ │ ├── Chunk.js │ │ ├── DoStatement.js │ │ ├── ElseClause.js │ │ ├── ElseifClause.js │ │ ├── ForGenericStatement.js │ │ ├── ForNumericStatement.js │ │ ├── FunctionDeclaration.js │ │ ├── Identifier.js │ │ ├── IfClause.js │ │ ├── IfStatement.js │ │ ├── IndexExpression.js │ │ ├── LocalStatement.js │ │ ├── LogicalExpression.js │ │ ├── MemberExpression.js │ │ ├── RepeatStatement.js │ │ ├── ReturnStatement.js │ │ ├── StringCallExpression.js │ │ ├── TableCallExpression.js │ │ ├── TableConstructorExpression.js │ │ ├── TableKeyString.js │ │ ├── UnaryExpression.js │ │ ├── WhileStatement.js │ │ └── index.js │ │ ├── utils.js │ │ └── walker.js ├── preload.js ├── protocols.js ├── providers │ ├── completion-provider.js │ ├── definition-provider.js │ ├── diagnostic-provider.js │ ├── format-provider.js │ ├── hover-provider.js │ ├── ldoc-provider.js │ ├── lib │ │ ├── awaiter.js │ │ ├── document-symbol.js │ │ ├── file-manager.js │ │ ├── linters.js │ │ ├── message.js │ │ ├── symbol-parser.js │ │ └── utils.js │ ├── rename-provider.js │ ├── signature-provider.js │ └── symbol-provider.js ├── server-ipc.js ├── server-stdio.js ├── server.js └── tracer.js ├── snippets ├── ldoc.json └── lua.json ├── stdlibs ├── 5_1.json ├── 5_2.json ├── 5_3.json ├── busted.json ├── love.json └── luajit-2_0.json └── test ├── test.engine.js ├── test.js └── textures ├── rr.lua ├── std.json └── test01.lua /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-const-assign": "warn", 16 | "no-this-before-super": "warn", 17 | "no-undef": "warn", 18 | "no-unreachable": "warn", 19 | "no-unused-vars": "warn", 20 | "constructor-super": "warn", 21 | "valid-typeof": "warn" 22 | } 23 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | luacoderassist-2.0.1.vsix 3 | server/lib/engine.rar 4 | -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/.vs/slnx.sqlite -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "test-extend", 8 | "program": "${workspaceFolder}\\server\\lib\\engine\\extend.js" 9 | }, 10 | { 11 | "type": "node", 12 | "request": "launch", 13 | "name": "调试engine", 14 | // "program": "${workspaceFolder}\\client\\extension.js" 15 | "program": "${workspaceFolder}\\test\\test.engine.js" 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "调试test", 21 | // "program": "${workspaceFolder}\\client\\extension.js" 22 | "program": "${workspaceFolder}\\test\\test.js" 23 | }, 24 | { 25 | "type": "extensionHost", 26 | "request": "launch", 27 | "name": "Launch Client", 28 | "runtimeExecutable": "${execPath}", 29 | "args": [ 30 | "--extensionDevelopmentPath=${workspaceRoot}" 31 | ], 32 | "stopOnEntry": false, 33 | "sourceMaps": true, 34 | "outFiles": [ 35 | "${workspaceRoot}/client/**/*.js" 36 | ], 37 | // "preLaunchTask": "npm: watch:client" 38 | }, 39 | { 40 | "type": "node", 41 | "request": "attach", 42 | "name": "Attach to Server", 43 | "address": "localhost", 44 | "protocol": "inspector", 45 | "port": 6004, 46 | "sourceMaps": true, 47 | "outFiles": [ 48 | "${workspaceRoot}/server/**/*.js" 49 | ] 50 | }, 51 | ] 52 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | jsconfig.json 6 | vsc-extension-quickstart.md 7 | .eslintrc.json 8 | -------------------------------------------------------------------------------- /3rd/luacheck/luacheck.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/3rd/luacheck/luacheck.exe -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "luacoderassist" extension will be documented in this file. 4 | 5 | ## 2.5.0 @ 2021-02-17 6 | 7 | * fixed: the default packaged luacheck executable is not macos/linux format, try to find a valid luacheck executable using `which luacheck` shell command. 8 | - #107 Ship UNIX luacheck binaries in addition to Windows ones 9 | - #106 Crashes after VSCode update 10 | - #104 LuaCoderAssist Crash 11 | - #102 Crash when luacheck is not available 12 | - #86 安裝後立即出錯,無法使用。 13 | 14 | ## 2.3.9 @ 2020-01-18 15 | 16 | - 优化:#85 当luacheckrc存在语法错误时,输出告警信息 17 | 18 | ## 2.3.8 @ 2020-01-18 19 | 20 | - 修复:#81 Thanks for the feedback from @tesselode 21 | 22 | ## 2.3.7 @ 2019-12-7 23 | 24 | - 优化:#79 修复格式化文档失效的问题 25 | 26 | ## 2.3.6 @ 2019-11-23 27 | 28 | - 优化:#78 支持递归获取父目录的`.luacompleterc`文件 29 | 30 | ## 2.3.5 @ 2019-9-15 31 | 32 | - 优化:新增`showFunctionOnly`配置,用来控制是否只显示函数符号列表,默认值为false 33 | - 优化:对于前向声明的函数变量,跳转到函数实现的地方 34 | 35 | ## 2.3.3 @ 2019-8-11 36 | 37 | - 修复:`pcall(function () ... end)`场景下,`function`函数体内无法提供局部符号表信息和自动补全功能 38 | - 优化:使用`:`调用`.`定义的函数时,不自动补全第一个参数 39 | 40 | ## 2.3.2 @ 2019-7-31 41 | 42 | - 修复:#71 43 | - 优化:#70 44 | - 优化:支持提供通过下标表达式的方式添加的字符串类型的域段的代码补全(形如如:foo['xx-yy'] = bar),暂不支持hover 45 | 46 | ## 2.3.1 @ 2019-7-31 47 | 48 | - 优化:符号搜索的比较方法改成根据range进行比较,解决部分场景下符号无法提供补全和悬浮提示功能 49 | - 优化:跳转到require的模块(非module定于的模块) 50 | 51 | ## 2.3.0 @ 2019-6-22 52 | 53 | - 优化:#58 支持配置是否自动插入函数参数列表,默认自动插入 54 | 55 | ## 2.2.13 @ 2019-6-02 56 | 57 | - 修复:#63 and #59 在`LuaCoderAssist.preloads`中配置了目录的情况下,插件崩溃的问题 58 | - 优化:在`LuaCoderAssist.preloads`中支持预加载目录下的所有lua文件 59 | 60 | ## 2.2.12 @ 2019-05-26 61 | 62 | - 修复:#62 设置`LuaCoderAssist.preloads`不生效的问题 63 | - 修复:#63 Text document completion failed, invalid regular expression 64 | 65 | ## 2.2.11 @ 2019-05-23 66 | 67 | - 修复:Fix backticks in the hover tooltips. From @alanfran 68 | 69 | ## 2.2.10 @ 2019-04-05 70 | 71 | - 优化:#58 Autocomplete only names of functions, not parentheses and arguments 72 | - 优化:利用调用参数简单推导返回值类型 73 | 74 | ## 2.2.9 @ 2019-02-16 75 | 76 | - 优化:#57 require and init.lua for AwesomeWM 77 | - 修复:#55 当文件中存在匿名函数时,无法提供文件内符号列表; 78 | - 修复:#56 修复访问null的数据 79 | - 修复:#49 CASE 3 80 | 81 | ## 2.2.8 @ 2019-01-20 82 | 83 | - 新增:#44 支持require文件路径补全 84 | - 优化:#49 支持在api描述文件中标志构造函数,已支持创建新对象,需要api的描述文件中对函数添加"kind": "constructor"属性 85 | - 修复:#55 当文件中存在匿名函数时,无法提供文件内符号列表 86 | - 修复:#56 修复访问null的数据 87 | 88 | ## 2.2.7 @ 2019-01-09 89 | 90 | - 修复:#52 当存在`a = foo(a, b); b = foo(a, b)`的表达式时,存在循环类型推导,导致死循环 91 | - 修复:#52 循环推导导致死循环,server无响应 92 | - 修复:#53 由于workspaceFolder为undefined导致server初始化失败 93 | 94 | ## 2.2.6 @ 2018-12-23 95 | 96 | - 修复:#49 通过赋值表达式`t.x = 123`动态地向表添加成员变量时,无法生效的问题 97 | - 修复:#49 问题2,当函数返回的是局部表时,两次调用该函数得到的表不应该是相同的,否则向其中一个表添加成员时,会影响所有该函数返回的表 98 | - 修复:#49 第三种场景,`local foo; function foo() end`存在两个foo符号的问题 99 | - 修复:#50 当返回一个函数调用(尾调用)时,函数的返回值类型只推导了尾调用函数的第一个返回值 100 | - 修复:形如`local xx = foo(params).foo()`的表达式,`xx`变量的类型推导失败的问题 101 | - 优化:#48区分符号的range和scope,解决符号outline不跟随鼠标的问题,但是该修改无法解决在表定义的外部定义函数的场景,比如:`local tbl={}; function tb.foo() end`,此时foo方法不在tb的range内 102 | 103 | ## 2.2.5 @ 2018-12-09 104 | 105 | - 新增:初步支持workspace工程,暂时还不支持动态增删workspace下的目录 106 | - 修复:foo('string'):此时提供的是string库的函数列表,应该根据函数返回值进行补全 107 | 108 | ## 2.2.3 @ 2018-11-18 109 | 110 | - 修复:当文件内存在循环依赖的表结构,例如`Class.__index=Class`时,无法提供完整的文件符号列表 111 | - 优化:支持("string"):upper()风格的字符串函数补全 112 | - 修复:for循环的key、value类型推导错误的问题 113 | - 优化:从README.md文件中删掉修改记录清单 114 | - 变更:插入函数的document统一采用带类型的格式 115 | 116 | ## 2.2.2 @ 2018-11-18 117 | 118 | - 修复:显式require love/jit等外部库时,无法提供补全信息(#45) 119 | - 优化:支持自定义扩展插件自带的std/love/jit等库符号(#46) 120 | - 优化:符号补全以及符号Hover功能在复杂的函数调用关系及参数场景下正常运行 121 | - 优化:支持显式通过_G来获取全局变量的代码补全 122 | - 优化:支持string类型变量及字面值字符串的函数补全 123 | 124 | ## 2.2.1 @ 2018-10-21 125 | 126 | - 修复:setmetatable在某些场景下无法生效的问题 127 | 128 | ## 2.2.0 @ 2018-10-21 129 | 130 | - 新增:支持不同文件使用相同的模块名 131 | - 优化:支持增删文件后的符号表增删处理 132 | - 优化:setmetatable使用场景优化,支持函数返回setmetatable的类型推导 133 | - 优化:支持在符号的定义处提供Hover信息 134 | - 优化:ldoc功能,只允许在函数定义的地方添加doc 135 | - 修复:foo().abc无法提供代码补全的问题 136 | - 修复:变量判空以及类型判断,防止非法访问错误 137 | - 修复:修复部分symbol没有定义state的bug 138 | - 修复:修复匿名函数内部符号无法补全的问题 139 | 140 | ## 2.1.3 @ 2018-10-13 141 | 142 | - 修复:在IO慢的机器上,由于kill了静态检查进程导致的代码补全过程中server异常重启 143 | - 优化:根据文件静态检查所需的时间自适应调整检查的延时时间 144 | - 优化:在1.28.0版本诊断信息中将错误码显示出来了,去掉告警消息前置的错误码信息 145 | - 优化:符号解析算法优化,增强上下文推导功能 146 | 147 | ## 2.1.2 @ 2018-10-09 148 | 149 | - 修复:当脚本中的静态检查告警从有到无时,告警信息无法清除的问题 150 | - 修复:第三方接口文档中的table类型数据没有定义fields字段时,导致代码补全弹出异常日志 151 | - 修复:因为脚本存在语法错误,保存时自动格式化导致异常日志弹出 152 | 153 | ## 2.1.1 @ 2018-10-07 154 | 155 | - 修复:支持对函数插入LDoc格式的代码注释 156 | - 修复:#41 自动补全在依赖模块通过多级目录指定时不生效的BUG 157 | 158 | ## 2.1.0 @ 2018-10-07 159 | 160 | - 新增:lua关键字几常用语句的代码片段 161 | - 新增:busted测试框架代码补全,删除原来的代码片段,通过按钮来控制busted是否使能 162 | - 优化:静态检查处理逻辑优化,保证同一时刻只有一个进程检查同一文件,保证最新的修改被检查 163 | - 修复:#39 #40 164 | 165 | ## 2.0.9 @ 2018-10-03 166 | 167 | - 优化:LUA 5.3和JIT接口文档优化,修复LUA 5.1接口文档的个别接口未匹配问题 168 | - 优化:luacheck静态检查配置,增加globals配置,通过luaversion匹配luacheck的std配置 169 | - 修复:当local _io = io时,_io无法自动补全的问题 170 | 171 | ## 2.0.8 @ 2018-10-02 172 | 173 | - 新增:更新符号解析方案,提供有限的符号类型推导功能 174 | - 新增:基于类型符号推导,提供更全面和强大的符号补全功能,支持.luacmpleterc文件配置 175 | - 新增:支持setmetatable,支持部分面向对象式编程风格 176 | - 新增:支持函数返回值类型推导和符号补全 177 | - 新增:文件符号列表支持树形显示父子关系 178 | - 新增:集成love、jit库的接口描述文件,用于支持代码补全和符号Hover信息提示 179 | - 修复:由于文件编码格式不一致导致的符号定义跳转行不准确的问题 180 | - 优化:在错误信息中显示luacheck的错误码 181 | - 优化:将部分luacheck的参数提取到配置luacheck.options中 182 | 183 | ## 1.3.8 @ 2018-03-17 184 | 185 | - Fix file detection for the luacheck option `--config`, contribute by `FireSiku` 186 | - Auto detect the `.luacheckrc` file for luacheck, contribute by `Positive07` 187 | - Add luacheck delay to improve experience while editing a large lua file 188 | - Add `LuaCoderAssist.luacheck.onSave` config 189 | - Add `LuaCoderAssist.luacheck.onTyping` config 190 | 191 | ## 1.3.7 @ 2018-02-10 192 | 193 | - fix issue #17. 194 | 195 | ## 1.3.6 @ 2018-01-31 196 | 197 | - add intellisense support for `self` using in function of nested table, ralate to issue #15. 198 | 199 | ## 1.3.5 @ 2018-01-30 200 | 201 | - fixed bug #16 202 | 203 | ## 1.3.4 @ 2018-01-28 204 | 205 | - add: resolve `self` key word to provide precise complete list, relate to issue #13 206 | 207 | ## 1.3.3 @ 2018-01-20 208 | 209 | - fix issue #12: fallback to vscode's default code-complete list when no defined symbol was found. 210 | 211 | ## 1.3.2 @ 2018-01-14 212 | 213 | - fix issue #9: add `--max-line-length` to luacheck using the `format.lineWidth` configuration. 214 | 215 | ## 1.3.1 @ 2018-01-05 216 | 217 | - fix: fixed bug in issue #7. 218 | - fix: update the description of `LuaCoderAssist.search.externalPaths` configuration. 219 | - add: add chinese description in README.md 220 | 221 | ## 1.3.0 @ 2017-12-03 222 | 223 | - add: code metric codelens 224 | - fix: symbols in new create file and remove symbols of deleted file. 225 | - remove: Extension Settings section in README.md 226 | 227 | ## 1.2.7 228 | 229 | - fix errors when open a file without `.lua` extension, see issue #3. 230 | 231 | ## 1.2.6 232 | 233 | - fix errors when open a file which has syntax error. 234 | - add `keepAfterClosed` option for luacheck diagnostics. 235 | 236 | ## 1.2.5 237 | 238 | - fix issue #3 239 | - add ldoc command to insert document for function. 240 | 241 | ## 1.2.3 242 | 243 | - fix bugs when module/file return with nonthing 244 | - add ldoc snippets 245 | 246 | ## 1.2.2 247 | 248 | - fix issue #2 249 | 250 | ## 1.2.1 251 | 252 | - update README.md 253 | - add VER 1.2.0 Release Notes 254 | 255 | ## 1.2.0 256 | 257 | - add format support 258 | - add return table syntax support 259 | 260 | ## 1.1.0 261 | 262 | - add support for rename local defined variables 263 | 264 | ## 1.0.2 265 | 266 | - add support for return symbol from a file 267 | 268 | ## [1.0.0] - 2017-10-29 269 | 270 | - Initial release 271 | 272 | ### Added 273 | 274 | - Document symbol support; 275 | - Goto definition, in or cross file; 276 | - Provide hover information about a symbol; 277 | - Diagnostics supported by using luaparse(for flycheck) and luacheck; 278 | - Code completion support; 279 | - Signature help support. 280 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua编程助手(Lua Coder Assistant) 2 | 3 | Lua 编程助手是一款能够为Lua开发人员提供智能帮助的基于VSCODE的插件 4 | 5 | Lua coder assistant is an vscode extension attempt to provide language intelligence for coders coding in lua language. 6 | 7 | ## 安装(Install) 8 | 9 | 本插件可在微软VSCODE插件商店中搜索`LuaCoderAssist`进行安装 10 | 11 | Search `LuaCoderAssist` in extension market of vscode and install. 12 | 13 | ## 功能(Features) 14 | 15 | - [x] 代码补全 16 | - [x] 类型推导(LIMITED)) 17 | - [x] 定义跳转 18 | - [x] 符号预览 19 | - [x] 静态检查 20 | - [x] 代码格式化 21 | - [x] 给函数插入LDoc格式的注释 22 | - [x] 支持LOVE、JIT、BUSTED代码补全 23 | - [x] 支持代码补全扩展 24 | - [x] 支持setmetatable通过__index模拟类继承的类成员补全 25 | - [x] 支持通过api接口描述文档来提供代码补全功能 26 | 27 | ### 当前已支持的功能(Supported) 28 | 29 | - **文件内符号列表(Document Symbols)** 30 | 31 | ![list](images/symbol-list.gif) 32 | 33 | - **符号定义跳转(Goto Definition)** 34 | 35 | ![goto](images/goto-def.gif) 36 | 37 | - **符号定义预览(Definition Peak)** 38 | 39 | ![peak](images/def-peak.gif) 40 | 41 | - **代码补全(Code Complete)** 42 | 43 | ![complete](images/complete.gif) 44 | 45 | - **函数特征帮助(Signatrue Help)** 46 | 47 | ![signature](images/signature.gif) 48 | 49 | - **静态检查(LuaCheck Support)** 50 | 51 | ![diagnostics](images/diagnostics.gif) 52 | 53 | - **代码格式化(Code Format)** 54 | 55 | ![format](images/format.gif) 56 | 57 | - **代码度量(Code Metrics)** 58 | 59 | ![metrics](images/metrics.gif) 60 | 61 | ## 依赖(Dependences) 62 | 63 | - [luaparse](https://github.com/oxyc/luaparse) 64 | - [luacheck](https://github.com/mpeterv/luacheck) 65 | - [lua-fmt](https://github.com/trixnz/lua-fmt) 66 | 67 | ----------------------------------------------------------------------------------------------------------- 68 | -------------------------------------------------------------------------------- /client/commands/busted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const protocols = require('../lib/protocols'); 4 | const languageClient = require('vscode-languageclient'); 5 | const vscode = require('vscode'); 6 | 7 | class Busted { 8 | constructor() { } 9 | 10 | /** 11 | * @param {languageClient.LanguageClient} connection 12 | */ 13 | init(context, connection) { 14 | if (this.connection) { 15 | return; 16 | } 17 | this.connection = connection; 18 | context.subscriptions.push( 19 | vscode.commands.registerCommand(commands.ACTIVATE, () => { 20 | this.activate(); 21 | }) 22 | ); 23 | 24 | context.subscriptions.push( 25 | vscode.commands.registerCommand(commands.DEACTIVATE, () => { 26 | this.deactivate(); 27 | }) 28 | ); 29 | 30 | this.bustedButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 2); 31 | this.populateButton(false); 32 | this.bustedButton.show(); 33 | } 34 | 35 | activate() { 36 | this.connection.sendRequest(protocols.BustedRequest.type, true) 37 | .then(this.onRequestSuccess.bind(this, true), this.onRequestFailure.bind(this, true)); 38 | } 39 | 40 | deactivate() { 41 | this.connection.sendRequest(protocols.BustedRequest.type, false) 42 | .then(this.onRequestSuccess.bind(this, false), this.onRequestFailure.bind(this, false)); 43 | } 44 | 45 | onRequestSuccess(activate) { 46 | console.log("request busted success, activate:" + activate); 47 | this.populateButton(activate); 48 | } 49 | 50 | onRequestFailure(activate) { 51 | console.log("request busted failure, activate:" + activate); 52 | } 53 | 54 | populateButton(activate) { 55 | Object.assign(this.bustedButton, activate ? ui.BT_ACTIVATE : ui.BT_DEACTIVATE); 56 | } 57 | } 58 | 59 | let _instance = undefined; 60 | 61 | /** 62 | * @returns {Busted} 63 | */ 64 | function instance() { 65 | if (_instance) { 66 | return _instance; 67 | } 68 | 69 | _instance = new Busted(); 70 | return _instance; 71 | } 72 | exports.instance = instance; 73 | 74 | const commands = { 75 | ACTIVATE: "LuaCoderAssist.busted.activate", 76 | DEACTIVATE: "LuaCoderAssist.busted.deactivate" 77 | } 78 | 79 | const ui = { 80 | BT_ACTIVATE: { 81 | text: 'Busted $(circle-slash)', 82 | tooltip: 'Deactivate busted mode', 83 | command: commands.DEACTIVATE, 84 | }, 85 | BT_DEACTIVATE: { 86 | text: 'Busted $(octoface)', 87 | tooltip: 'Activate busted mode', 88 | command: commands.ACTIVATE, 89 | }, 90 | } -------------------------------------------------------------------------------- /client/commands/codemetrics-details.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode_1 = require('vscode'); 4 | const opn_1 = require('opn'); 5 | 6 | function createQuickPickItem(label, description, detail) { 7 | return { 8 | label: label, 9 | description: description, 10 | detail: detail, 11 | onDidPressKey: (key) => { 12 | return opn_1('https://github.com/philbooth/escomplex#metrics'); 13 | } 14 | } 15 | } 16 | 17 | function makeQuickPickItems(metrics) { 18 | let items = []; 19 | items.push(createQuickPickItem( 20 | `$(link-external) Logical Lines: ${metrics.sloc.logical}`, 21 | 'A count of the imperative statements.', 22 | 'click to view more...' 23 | )); 24 | 25 | items.push(createQuickPickItem( 26 | `$(link-external) Physical Lines: ${metrics.sloc.physical}`, 27 | 'The number of lines in a module or function.', 28 | 'click to view more...' 29 | )); 30 | 31 | items.push(createQuickPickItem( 32 | `$(link-external) Cyclomatic Complexity: ${metrics.cyclomatic}`, 33 | 'Measures the complexity of the function, lower is better.', 34 | 'click to view more...' 35 | )); 36 | 37 | items.push(createQuickPickItem( 38 | `$(link-external) Maintainability Index: ${metrics.maintainability}`, 39 | 'The ease to repair or replace faulty or worn-out components, higher is better.', 40 | 'click to view more...' 41 | )); 42 | 43 | items.push(createQuickPickItem( 44 | `$(link-external) Number of Parameters: ${metrics.params}`, 45 | 'The number of parameters of a function, lower is better.', 46 | 'click to view more...' 47 | )); 48 | 49 | let cyclomaticDensity = (metrics.cyclomaticDensity === Infinity ? 1 : metrics.cyclomaticDensity).toFixed(); 50 | items.push(createQuickPickItem( 51 | `$(link-external) Cyclomatic Complexity Density: ${cyclomaticDensity}`, 52 | 'A percentage of the Cyclomatic Complexity and Logical Lines, lower is better.', 53 | 'click to view more...' 54 | )); 55 | 56 | return items; 57 | } 58 | 59 | class ShowMetricsDetails { 60 | constructor() { 61 | 62 | } 63 | 64 | showDetails(metrics) { 65 | vscode_1.window.showQuickPick( 66 | makeQuickPickItems(metrics), 67 | { 68 | matchOnDescription: true, 69 | matchOnDetail: true, 70 | placeHolder: 'Click to review more details introduction', 71 | } 72 | ).then(item => { 73 | item && item.onDidPressKey(); 74 | }); 75 | } 76 | }; 77 | 78 | 79 | exports.ShowMetricsDetails = ShowMetricsDetails; 80 | exports.ShowMetricsDetailsCommand = 'LuaCoderAssist.metrics.details'; 81 | -------------------------------------------------------------------------------- /client/commands/ldoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const protocols = require('../lib/protocols'); 4 | const utils = require('../lib/utils'); 5 | const logger = require('../lib/logger'); 6 | const vscode = require('vscode'); 7 | 8 | class LDocCommand { 9 | constructor(connection) { 10 | this.connection = connection; 11 | connection.onRequest(protocols.LDocRequest.type, (params) => { 12 | this.onResponse(params); 13 | }); 14 | } 15 | 16 | onRequest() { 17 | let activeDoc = utils.getActiveLuaDocument(); 18 | if (!activeDoc) { 19 | return; 20 | } 21 | 22 | let position = activeDoc.validatePosition(utils.getCursorPosition()); 23 | let params = { uri: activeDoc.uri.toString(), position: position } 24 | this.connection.sendRequest(protocols.LDocRequest.type, params).then(this.onResponse.bind(this), (e) => { 25 | logger.Logger.error('onRequest for LDoc failed: ', e); 26 | }); 27 | } 28 | 29 | onResponse(params) { 30 | if (params.message) { 31 | switch (params.type) { 32 | case 'error': 33 | logger.Logger.error(params.message); 34 | break; 35 | case 'warn': 36 | logger.Logger.warn(params.message); 37 | break; 38 | case 'info': 39 | logger.Logger.log(params.message); 40 | default: 41 | break; 42 | } 43 | return; 44 | } 45 | 46 | return insertDoc(params); 47 | } 48 | }; 49 | 50 | function insertDoc(params) { 51 | let activeTextEditor = vscode.window.activeTextEditor; 52 | let snippet = new vscode.SnippetString(params.doc); 53 | let position = new vscode.Position(params.location.line, params.location.character); 54 | activeTextEditor.insertSnippet(snippet, position); 55 | return; 56 | } 57 | 58 | exports.LDocCommand = LDocCommand; 59 | exports.LDocCommandName = "LuaCoderAssist.ldoc"; 60 | 61 | -------------------------------------------------------------------------------- /client/extension.js: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 'use strict'; 6 | const ldoc_1 = require('./commands/ldoc'); 7 | const busted_1 = require('./commands/busted'); 8 | const showMetricDetails = require('./commands/codemetrics-details'); 9 | const path = require("path"); 10 | const vscode = require("vscode"); 11 | const languageclient = require("vscode-languageclient"); 12 | const logger_1 = require('./lib/logger'); 13 | const CodeMetricsProvider = require('./providers/codemetrics-provider'); 14 | 15 | function activate(context) { 16 | let serverModule = context.asAbsolutePath(path.join('server', 'server-ipc.js')); 17 | let debugOptions = { execArgv: ["--nolazy", "--inspect=6004"] }; 18 | let serverOptions = { 19 | run: { module: serverModule, transport: languageclient.TransportKind.ipc }, 20 | debug: { module: serverModule, transport: languageclient.TransportKind.ipc, options: debugOptions } 21 | }; 22 | 23 | let clientOptions = { 24 | documentSelector: { scheme: 'file', language: 'lua' }, 25 | synchronize: { 26 | configurationSection: 'LuaCoderAssist', 27 | fileEvents: [vscode.workspace.createFileSystemWatcher('**/*.lua', false, true, false)] 28 | } 29 | }; 30 | 31 | logger_1.Logger.configure(); 32 | 33 | let connection = new languageclient.LanguageClient('LuaCoderAssist', serverOptions, clientOptions); 34 | context.subscriptions.push(connection.start()); 35 | 36 | context.subscriptions.push( 37 | vscode.commands.registerCommand(ldoc_1.LDocCommandName, () => { 38 | let ldoc = new ldoc_1.LDocCommand(connection); 39 | ldoc.onRequest(); 40 | }) 41 | ); 42 | 43 | context.subscriptions.push( 44 | vscode.languages.registerCodeLensProvider('lua', 45 | new CodeMetricsProvider.CodeMetricsProvider() 46 | ) 47 | ); 48 | 49 | context.subscriptions.push( 50 | vscode.commands.registerCommand(showMetricDetails.ShowMetricsDetailsCommand, (params) => { 51 | let showDetails = new showMetricDetails.ShowMetricsDetails(); 52 | showDetails.showDetails(params); 53 | }) 54 | ); 55 | 56 | busted_1.instance().init(context, connection) 57 | } 58 | 59 | exports.activate = activate; 60 | 61 | 62 | -------------------------------------------------------------------------------- /client/lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var GlyphChars; 4 | (function (GlyphChars) { 5 | GlyphChars['CheckPass'] = '\u2714'; 6 | GlyphChars['CheckFail'] = '\u2718'; 7 | })(GlyphChars = exports.GlyphChars || (exports.GlyphChars = {})); 8 | 9 | -------------------------------------------------------------------------------- /client/lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode_1 = require('vscode'); 4 | 5 | class Logger { 6 | static configure() { 7 | this.output = vscode_1.window.createOutputChannel("LuaCoderAssistClient"); 8 | } 9 | 10 | static log(message, ...params) { 11 | if (this.output !== undefined) { 12 | this.output.appendLine([message, ...params].join(' ')); 13 | } 14 | } 15 | 16 | static error(message, ...params) { 17 | if (this.output !== undefined) { 18 | this.output.appendLine(["[ERROR]", message, ...params].join(' ')); 19 | } 20 | } 21 | 22 | static warn(message, ...params) { 23 | if (this.output !== undefined) { 24 | this.output.appendLine(["[WARN]", message, ...params].join(' ')); 25 | } 26 | } 27 | } 28 | 29 | exports.Logger = Logger; 30 | -------------------------------------------------------------------------------- /client/lib/metrics/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const analysis = require('./lib/analysis'); 4 | 5 | exports.module = analysis.module; 6 | exports.project = analysis.project; -------------------------------------------------------------------------------- /client/lib/metrics/lib/analysis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const walker = require('./walker'); 4 | const escomplex = require('escomplex'); 5 | const luaparse = require('luaparse'); 6 | const fs = require('fs'); 7 | const adds = require('autodetect-decoder-stream'); 8 | 9 | const defaultConfig = { 10 | logicalor: true, 11 | switchcase: true, 12 | forin: true, 13 | newmi: true 14 | }; 15 | 16 | /** 17 | * Analysis the code metrics for one module 18 | * @param {String} filePath The path of the module file. 19 | * @param {Object} tree [Optional] The abstract syntax tree of the module, 20 | * generated by luaparse with `locations` enable 21 | * @returns {Object} The report of the module if anaysis success 22 | */ 23 | exports.module = (filePath, content) => { 24 | return escomplex.analyse( 25 | luaparse.parse(content, { locations: true, comments: false }), 26 | walker, 27 | defaultConfig 28 | ); 29 | } 30 | 31 | /** 32 | * Analysis the code metrics for project files 33 | * @param {String} filesPath The path of the project files. 34 | * @returns {Object} The reports of the project if anaysis success 35 | */ 36 | exports.project = (filesPath) => { 37 | return analyseProject(filesPath); 38 | } 39 | 40 | function analyseProject(filesPath) { 41 | if (!Array.isArray(filesPath)) { 42 | return Promise.reject("param 'filesPath' is not an Array!"); 43 | } 44 | 45 | let jobs = filesPath.map(filePath => { 46 | return new Promise((resolve) => { 47 | let stream = fs.createReadStream(filePath).pipe(new adds()); 48 | stream.collect((error, data) => { 49 | resolve({ 50 | path: filePath, 51 | ast: luaparse.parse(data, { locations: true, comments: false }) 52 | }); 53 | }); 54 | }); 55 | }); 56 | return Promise.all(jobs) 57 | .then(modules => { 58 | return escomplex.analyse(modules, walker, defaultConfig); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /client/lib/metrics/lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function safeName(node) { 4 | if (node) { 5 | return node.name || safeName(node.identifier) || safeName(node.base); 6 | } else { 7 | return ''; 8 | } 9 | } 10 | 11 | exports.safeName = safeName; 12 | 13 | exports.processRequire = processRequire; 14 | function processRequire(node) { 15 | let moduleName = node.argument || node.arguments[0]; 16 | return createDependency(node, resolveRequireDependency(moduleName, "require")); 17 | } 18 | 19 | function resolveRequireDependency(dependency) { 20 | if (dependency.type === 'StringLiteral') { 21 | return dependency.value; 22 | } 23 | 24 | return '* dynamic dependency *'; 25 | } 26 | 27 | function createDependency(node, path, type) { 28 | return { 29 | line: node.loc.start.line, 30 | path: path, 31 | type: type 32 | }; 33 | } 34 | 35 | exports.processPCall = processPCall; 36 | function processPCall(node) { 37 | return createDependency(node, resolvePCallDependency(node.arguments[1]), 'pcall'); 38 | } 39 | 40 | function resolvePCallDependency(dependency) { 41 | if (dependency.type === 'StringLiteral') { 42 | return dependency.value; 43 | } 44 | 45 | return '* dynamic dependency *'; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /client/lib/metrics/lib/walker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This module is copy and modified from escompex-ast-moz for lua language 5 | */ 6 | const check = require('check-types'); 7 | const syntax = require('../syntax'); 8 | 9 | exports.walk = walk; 10 | 11 | function walk(tree, settings, callbacks) { 12 | let syntaxes = syntax.get(settings); 13 | 14 | visitNodes(tree.body); 15 | 16 | function visitNodes(nodes, assignedName) { 17 | nodes.forEach(function (node) { 18 | visitNode(node, assignedName); 19 | }); 20 | } 21 | 22 | function visitNode(node, assignedName) { 23 | var syntax; 24 | 25 | if (check.object(node)) { 26 | syntax = syntaxes[node.type]; 27 | 28 | if (check.object(syntax)) { 29 | callbacks.processNode(node, syntax); 30 | 31 | if (syntax.newScope) { 32 | syntax.newScope(node, syntax, callbacks.createScope); 33 | } 34 | 35 | visitChildren(node); 36 | 37 | if (syntax.newScope) { 38 | callbacks.popScope(); 39 | } 40 | } 41 | } 42 | } 43 | 44 | function visitChildren(node) { 45 | var syntax = syntaxes[node.type]; 46 | 47 | if (check.array(syntax.children)) { 48 | syntax.children.forEach(function (child) { 49 | visitChild( 50 | node[child], 51 | check.function(syntax.assignableName) ? syntax.assignableName(node) : '' 52 | ); 53 | }); 54 | } 55 | } 56 | 57 | function visitChild(child, assignedName) { 58 | var visitor = check.array(child) ? visitNodes : visitNode; 59 | visitor(child, assignedName); 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/AssignmentStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, "=", undefined, ["variables", "init"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/BinaryExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, (node) => { 11 | return node.operator; 12 | }, undefined, ["left", "right"]); 13 | } 14 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/BooleanLiteral.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise( 11 | 0, 0, undefined, 12 | function (node) { 13 | return node.value; 14 | } 15 | ); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/BreakStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, 'break'); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/CallExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise( 12 | 0, 0, '()', undefined, ['base', 'arguments'], undefined, undefined, 13 | function (node) { 14 | if (node.base.type === 'Identifier' && node.base.name === 'require') { 15 | return utils.processRequire(node); 16 | } else if (node.base.type === 'Identifier' && node.base.name === 'pcall') { 17 | if (node.arguments[0].type === 'Identifier' && node.arguments[0].name === 'require') { 18 | return utils.processPCall(node); 19 | } 20 | } 21 | } 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/CallStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, undefined, undefined, ["expression"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/Chunk.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, undefined, undefined, ["body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/DoStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, "do", undefined, ["body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/ElseClause.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 1, "else", undefined, ["body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/ElseifClause.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, "elseif", undefined, ["condition", "body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/ForGenericStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 1, "forin", undefined, ["variables", "iterators", "body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/ForNumericStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 1, "for", undefined, ["variables", "start", "end", "step", "body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/FunctionDeclaration.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise(1, 0, "function", 12 | (node) => { 13 | return sigName(node.identifier); 14 | }, 15 | ["identifier", "parameters", "body"], 16 | (node) => { 17 | return utils.safeName(node); 18 | }, 19 | (node, syntax, createScope) => { 20 | createScope(sigName(node.identifier), node.loc, node.parameters.length); 21 | }); 22 | } 23 | 24 | function sigName(node) { 25 | let names = []; 26 | 27 | function _sigNameHelper(node) { 28 | if (node && node.base) { 29 | _sigNameHelper(node.base); 30 | names.push(node.indexer || "."); 31 | names.push(utils.safeName(node)); 32 | } else { 33 | names.push(utils.safeName(node)); 34 | } 35 | } 36 | 37 | _sigNameHelper(node); 38 | return names.join(""); 39 | } 40 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/GotoStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, 'goto', 11 | (node) => { 12 | return node.label.name; 13 | }, 14 | ['label']); 15 | } 16 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/Identifier.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise(0, 0, undefined, 12 | (node) => { 13 | return utils.safeName(node); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/IfClause.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 1, "if", undefined, ["condition", "body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/IfStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, undefined, undefined, ["clauses"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/IndexExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise(0, 0, "[]", 12 | (node) => { 13 | return utils.safeName(node.base); 14 | }, ["base", "index"]); 15 | } 16 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/LabelStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, '::label::', undefined, ['label']); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/LocalStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise( 12 | (node) => { 13 | return node.variables.length; 14 | }, 15 | 0, "local", 16 | (node) => { 17 | return utils.safeName(node.variables[0]); 18 | }, ["variables", "init"]); 19 | } 20 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/LogicalExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise( 11 | 0, 12 | (node) => { 13 | return node.operator == 'or' ? 1 : 0; 14 | }, 15 | (node) => { 16 | return node.operator; 17 | }, undefined, ["left", "right"]); 18 | } 19 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/MemberExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, 11 | (node) => { 12 | return node.indexer; 13 | }, undefined, ["identifier", "base"]); 14 | } 15 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/NilLiteral.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/NumericLiteral.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise( 11 | 0, 0, undefined, 12 | function (node) { 13 | return node.value; 14 | } 15 | ); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/RepeatStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, "repeat", undefined, ["condition", "body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/ReturnStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, "return", undefined, ["arguments"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/StringCallExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise(0, 0, "(\"\")", undefined, ["base", "argument"], undefined, undefined, 12 | (node) => { 13 | if (node.base.type === 'Identifier' && node.base.name === 'require') { 14 | return utils.processRequire(node); 15 | } 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/StringLiteral.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise( 11 | 0, 0, undefined, 12 | (node) => { 13 | return '"' + node.value + '"'; 14 | } 15 | ); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/TableCallExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, "({})", undefined, ["base", "arguments"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/TableConstructorExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, "{}", undefined, ["fields"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/TableKey.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, 11 | undefined, undefined, ["key", "value"]); 12 | } 13 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/TableKeyString.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, 11 | undefined, undefined, ["key", "value"]); 12 | } 13 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/UnaryExpression.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'), 6 | utils = require('../lib/utils'); 7 | 8 | exports.get = get; 9 | 10 | function get() { 11 | return traits.actualise(0, 0, 12 | (node) => { 13 | return node.operator; 14 | }, 15 | (node) => { 16 | return utils.safeName(node.argument); 17 | }, 18 | ["argument"]); 19 | } 20 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/VarargLiteral.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, undefined, '...'); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/WhileStatement.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(1, 0, 'while', undefined, ["condition", "body"]); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/index.js: -------------------------------------------------------------------------------- 1 | /*jshint nomen:false */ 2 | /*globals require, exports, __dirname */ 3 | 4 | 'use strict'; 5 | 6 | var fs = require('fs'), 7 | loaded = false, 8 | syntaxModules = []; 9 | 10 | exports.get = getSyntax; 11 | 12 | function getSyntax(settings) { 13 | var syntax = {}, name; 14 | 15 | if (loaded === false) { 16 | loadSyntaxModules(); 17 | loaded = true; 18 | } 19 | 20 | for (name in syntaxModules) { 21 | if (syntaxModules.hasOwnProperty(name)) { 22 | setSyntax(syntax, name, settings); 23 | } 24 | } 25 | 26 | return syntax; 27 | } 28 | 29 | function loadSyntaxModules() { 30 | var fileNames, i, fileName, components; 31 | 32 | fileNames = getSyntaxFileNames(); 33 | 34 | for (i = 0; i < fileNames.length; i += 1) { 35 | fileName = fileNames[i]; 36 | components = fileName.split('.'); 37 | 38 | if (isSyntaxDefinition(fileName, components)) { 39 | loadSyntaxModule(components[0]); 40 | } 41 | } 42 | } 43 | 44 | function getSyntaxFileNames() { 45 | return fs.readdirSync(__dirname); 46 | } 47 | 48 | function isSyntaxDefinition(fileName, components) { 49 | if (fs.statSync(pathify(__dirname, fileName)).isFile()) { 50 | return components.length === 2 && components[0] !== 'index' && components[1] === 'js'; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | function pathify(directory, fileName) { 57 | return directory + '/' + fileName; 58 | } 59 | 60 | function loadSyntaxModule(name) { 61 | syntaxModules[name] = require(pathify('.', name)); 62 | } 63 | 64 | function setSyntax(syntax, name, settings) { 65 | syntax[name] = syntaxModules[name].get(settings); 66 | } 67 | 68 | -------------------------------------------------------------------------------- /client/lib/metrics/syntax/tableValue.js: -------------------------------------------------------------------------------- 1 | /*globals require, exports */ 2 | 3 | 'use strict'; 4 | 5 | var traits = require('escomplex-traits'); 6 | 7 | exports.get = get; 8 | 9 | function get() { 10 | return traits.actualise(0, 0, undefined, undefined, ['value']); 11 | } 12 | -------------------------------------------------------------------------------- /client/lib/protocols.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var LDocRequest; 4 | (function (LDocRequest) { 5 | LDocRequest.type = { get method() { return "LuaCoderAssist/LDoc"; } } 6 | })(LDocRequest = exports.LDocRequest || (exports.LDocRequest = {})); 7 | 8 | var BustedRequest; 9 | (function (BustedRequest) { 10 | BustedRequest.type = { get method() { return "LuaCoderAssist/Busted"; } } 11 | })(BustedRequest = exports.BustedRequest || (exports.BustedRequest = {})); 12 | -------------------------------------------------------------------------------- /client/lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode_1 = require('vscode'); 4 | 5 | function getActiveLuaDocument() { 6 | const activeTextEditor = vscode_1.window.activeTextEditor; 7 | if (activeTextEditor === undefined || activeTextEditor.document === undefined) { 8 | return null; 9 | } 10 | 11 | if (activeTextEditor.document.languageId != "lua") { 12 | return null; 13 | } 14 | 15 | return activeTextEditor.document; 16 | } 17 | 18 | exports.getActiveLuaDocument = getActiveLuaDocument; 19 | 20 | function getCursorPosition() { 21 | const activeTextEditor = vscode_1.window.activeTextEditor; 22 | return activeTextEditor.selection.active; 23 | } 24 | 25 | exports.getCursorPosition = getCursorPosition; 26 | -------------------------------------------------------------------------------- /client/providers/codemetrics-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const code_metrics_1 = require('../lib/metrics'); 4 | const uri_1 = require('vscode-uri'); 5 | const vscode_1 = require('vscode'); 6 | const show_details = require('../commands/codemetrics-details'); 7 | const constants_1 = require('../lib/constants'); 8 | 9 | /** 10 | * copy and modified from escomplex 11 | * @param {Object} funcMetric function metrics from escomplex 12 | */ 13 | function calculateFunctionMaintainabilityIndex(funcMetric) { 14 | let effort = funcMetric.halstead.effort; 15 | let cyclomatic = funcMetric.cyclomatic; 16 | let lloc = funcMetric.sloc.logical; 17 | 18 | if (cyclomatic === 0) { 19 | throw new Error('Encountered function with cyclomatic complexity zero!'); 20 | } 21 | 22 | let maintainability = 23 | 171 - 24 | (3.42 * Math.log(effort)) - 25 | (0.23 * Math.log(cyclomatic)) - 26 | (16.2 * Math.log(lloc)); 27 | 28 | if (maintainability > 171) { 29 | maintainability = 171; 30 | } 31 | 32 | maintainability = Math.max(0, (maintainability * 100) / 171); 33 | 34 | funcMetric.maintainability = parseFloat(maintainability).toFixed(); 35 | } 36 | 37 | function getCodeMetricSettings() { 38 | let coderSettings = vscode_1.workspace.getConfiguration("LuaCoderAssist"); 39 | return coderSettings.get("metric") || {}; 40 | } 41 | 42 | function checkMetrics(lloc, ploc, cyclomatic, maintainability) { 43 | let settings = getCodeMetricSettings(); 44 | return { 45 | lloc: (lloc <= settings.logicalLineMax && ploc <= settings.physicalLineMax) ? constants_1.GlyphChars.CheckPass : constants_1.GlyphChars.CheckFail, 46 | cyclomatic: (cyclomatic <= settings.cyclomaticMax) ? constants_1.GlyphChars.CheckPass : constants_1.GlyphChars.CheckFail, 47 | maintainability: (maintainability >= settings.maintainabilityMin) ? constants_1.GlyphChars.CheckPass : constants_1.GlyphChars.CheckFail 48 | }; 49 | } 50 | 51 | class CodeMetricsProvider { 52 | constructor(coder) { 53 | this.coder = coder; 54 | this.metrics = {}; 55 | } 56 | 57 | analyseCodeMetrics(document) { 58 | return new Promise((resolve, reject) => { 59 | setTimeout(() => { 60 | let uri = document.uri; 61 | let filePath = uri_1.default.parse(uri).fsPath; 62 | resolve(code_metrics_1.module(filePath, document.getText())); 63 | }, 0); 64 | }); 65 | } 66 | 67 | provideCodeLenses(document) { 68 | let settings = getCodeMetricSettings(); 69 | if (!settings.enable) { 70 | return; 71 | } 72 | 73 | return this.analyseCodeMetrics(document) 74 | .then(report => { 75 | return report.functions.map(fn => { 76 | let codeLens = new vscode_1.CodeLens( 77 | new vscode_1.Range(fn.line - 1, 0, fn.line - 1, 0) 78 | ); 79 | 80 | codeLens.data = fn; 81 | 82 | return codeLens; 83 | }); 84 | }); 85 | } 86 | 87 | resolveCodeLens(codeLens) { 88 | let lloc = codeLens.data.sloc.logical; 89 | let ploc = codeLens.data.sloc.physical; 90 | let cyclomatic = codeLens.data.cyclomatic; 91 | let maintainability; 92 | 93 | codeLens.data.maintainability || calculateFunctionMaintainabilityIndex(codeLens.data); 94 | maintainability = codeLens.data.maintainability; 95 | 96 | let results = checkMetrics(lloc, ploc, cyclomatic, maintainability); 97 | codeLens.command = { 98 | title: `S(${lloc}/${ploc}${results.lloc})|C(${cyclomatic}${results.cyclomatic})|M(${maintainability}${results.maintainability})`, 99 | command: show_details.ShowMetricsDetailsCommand, 100 | arguments: [codeLens.data] 101 | }; 102 | return codeLens; 103 | } 104 | }; 105 | 106 | exports.CodeMetricsProvider = CodeMetricsProvider; 107 | -------------------------------------------------------------------------------- /images/complete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/complete.gif -------------------------------------------------------------------------------- /images/def-peak.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/def-peak.gif -------------------------------------------------------------------------------- /images/diagnostics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/diagnostics.gif -------------------------------------------------------------------------------- /images/format.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/format.gif -------------------------------------------------------------------------------- /images/goto-def.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/goto-def.gif -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/icon.png -------------------------------------------------------------------------------- /images/ldoc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/ldoc.gif -------------------------------------------------------------------------------- /images/metrics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/metrics.gif -------------------------------------------------------------------------------- /images/rename.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/rename.gif -------------------------------------------------------------------------------- /images/return-table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/return-table.gif -------------------------------------------------------------------------------- /images/signature.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/signature.gif -------------------------------------------------------------------------------- /images/symbol-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liwangqian/LuaCoderAssist/6bc32272bc3217c2533d1375f42002c8df195b26/images/symbol-list.gif -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "lib": [ 6 | "es6" 7 | ] 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luacoderassist", 3 | "displayName": "LuaCoderAssist", 4 | "description": "lua编程助手,包括代码补全、符号预览&跳转、函数特征帮助、符号类型推导、静态检查、格式化、代码度量等功能", 5 | "icon": "images/icon.png", 6 | "version": "2.5.0", 7 | "publisher": "liwangqian", 8 | "engines": { 9 | "vscode": "^1.25.0" 10 | }, 11 | "categories": [ 12 | "Programming Languages", 13 | "Linters", 14 | "Snippets", 15 | "Formatters" 16 | ], 17 | "keywords": [ 18 | "lua", 19 | "completion", 20 | "luacheck", 21 | "format", 22 | "metric", 23 | "love", 24 | "jit" 25 | ], 26 | "activationEvents": [ 27 | "onLanguage:lua" 28 | ], 29 | "main": "./client/extension", 30 | "contributes": { 31 | "commands": [ 32 | { 33 | "command": "LuaCoderAssist.ldoc", 34 | "title": "插入注释(Insert Lua Document)" 35 | }, 36 | { 37 | "command": "LuaCoderAssist.metrics.details", 38 | "title": "显示代码度量详细信息(Show Code Metrics Details)" 39 | }, 40 | { 41 | "command": "LuaCoderAssist.busted.activate", 42 | "title": "进入busted模式(Enter busted mode)" 43 | }, 44 | { 45 | "command": "LuaCoderAssist.busted.deactivate", 46 | "title": "退出busted模式(Exit busted mode)" 47 | } 48 | ], 49 | "menus": { 50 | "editor/context": [ 51 | { 52 | "command": "LuaCoderAssist.ldoc", 53 | "when": "resourceLangId == lua", 54 | "group": "1_modification" 55 | } 56 | ] 57 | }, 58 | "configuration": { 59 | "type": "Object", 60 | "title": "Lua Coder Assistant Configuration", 61 | "properties": { 62 | "LuaCoderAssist.enable": { 63 | "type": "boolean", 64 | "default": true, 65 | "description": "Enable/disable the extension." 66 | }, 67 | "LuaCoderAssist.debug": { 68 | "type": "boolean", 69 | "default": false, 70 | "description": "Debug information output enable." 71 | }, 72 | "LuaCoderAssist.preloads": { 73 | "type": "array", 74 | "default": [], 75 | "description": "Preload modules path, can be file or directory. DO NOT config a dirctory with a lot files for performance consideration. If a directory configured has a init.lua file, the file will be loaded instead of all file under this directory, otherwise, files in the directory and its subdir will be loaded" 76 | }, 77 | "LuaCoderAssist.useLove": { 78 | "type": "boolean", 79 | "default": false, 80 | "description": "Use `LOVE` libraries." 81 | }, 82 | "LuaCoderAssist.useJit": { 83 | "type": "boolean", 84 | "default": false, 85 | "description": "Use `LuaJit` libraries." 86 | }, 87 | "LuaCoderAssist.luaPath": { 88 | "type": "string", 89 | "default": "", 90 | "description": "Provide the lua path to help find the rigth module path, when there is some modules with same file name.", 91 | "examples": [ 92 | "{workspaceRoot}/pathA/subPath0/subPath1/?.lua;{workspaceRoot}/pathB/subPath0/subPath1/?.lua" 93 | ] 94 | }, 95 | "LuaCoderAssist.search.filters": { 96 | "type": "array", 97 | "default": [], 98 | "description": "Filter patterns for file search." 99 | }, 100 | "LuaCoderAssist.search.externalPaths": { 101 | "type": "array", 102 | "default": [], 103 | "description": "External paths outside the workspaceroot to be search." 104 | }, 105 | "LuaCoderAssist.search.followLinks": { 106 | "type": "boolean", 107 | "default": false, 108 | "description": "Whether the links to be search." 109 | }, 110 | "LuaCoderAssist.luaparse.luaversion": { 111 | "type": "number", 112 | "default": 5.1, 113 | "enum": [ 114 | 5.1, 115 | 5.2, 116 | 5.3 117 | ], 118 | "description": "The lua version, for grammer match." 119 | }, 120 | "LuaCoderAssist.luaparse.allowDefined": { 121 | "type": "boolean", 122 | "default": false, 123 | "description": "Allow defining globals implicitly by setting them." 124 | }, 125 | "LuaCoderAssist.symbol.showAnonymousFunction": { 126 | "type": "boolean", 127 | "default": true, 128 | "description": "Show anonymous function in document symbol list." 129 | }, 130 | "LuaCoderAssist.symbol.showFunctionOnly": { 131 | "type": "boolean", 132 | "default": false, 133 | "description": "Only function is shown in document symbol list." 134 | }, 135 | "LuaCoderAssist.luacheck.enable": { 136 | "type": "boolean", 137 | "default": true, 138 | "description": "Enable/Disable luacheck for static diagnostics." 139 | }, 140 | "LuaCoderAssist.luacheck.automaticOption": { 141 | "type": "boolean", 142 | "default": true, 143 | "description": "Define the luacheck cli options automatically." 144 | }, 145 | "LuaCoderAssist.luacheck.options": { 146 | "type": "array", 147 | "default": [ 148 | "-m", 149 | "-t" 150 | ], 151 | "description": "Cli options for luacheck, see [luacheck](https://luacheck.readthedocs.io/en/stable/cli.html#command-line-options) for details." 152 | }, 153 | "LuaCoderAssist.luacheck.onSave": { 154 | "type": "boolean", 155 | "default": true, 156 | "description": "Run luacheck when file saved." 157 | }, 158 | "LuaCoderAssist.luacheck.onTyping": { 159 | "type": "boolean", 160 | "default": true, 161 | "description": "Run luacheck when file changed." 162 | }, 163 | "LuaCoderAssist.luacheck.execPath": { 164 | "type": "string", 165 | "default": null, 166 | "description": "Path of the luacheck excutable.(@ref https://github.com/mpeterv/luacheck)" 167 | }, 168 | "LuaCoderAssist.luacheck.std": { 169 | "type": "array", 170 | "default": [ 171 | "busted" 172 | ], 173 | "description": "Set standard globals.(@ref https://luacheck.readthedocs.io/en/stable/cli.html#command-line-options)" 174 | }, 175 | "LuaCoderAssist.luacheck.ignore": { 176 | "type": "array", 177 | "default": [], 178 | "description": "Filter out warnings matching patterns.(@ref http://luacheck.readthedocs.io/en/stable/cli.html)" 179 | }, 180 | "LuaCoderAssist.luacheck.globals": { 181 | "type": "array", 182 | "default": [], 183 | "description": "Add read-only global variables or fields.(@ref https://luacheck.readthedocs.io/en/stable/cli.html)" 184 | }, 185 | "LuaCoderAssist.luacheck.jobs": { 186 | "type": "integer", 187 | "default": 1, 188 | "description": "Number of jobs for parallel check.(@ref http://luacheck.readthedocs.io/en/stable/cli.html)" 189 | }, 190 | "LuaCoderAssist.luacheck.fileSizeLimit": { 191 | "type": "integer", 192 | "default": 100, 193 | "description": "File size (KB) limit for luacheck, performance consideration." 194 | }, 195 | "LuaCoderAssist.luacheck.maxProblems": { 196 | "type": "integer", 197 | "default": 250, 198 | "description": "Max problems to show." 199 | }, 200 | "LuaCoderAssist.luacheck.configFilePath": { 201 | "type": "string", 202 | "default": "", 203 | "description": "The path of '.luacheckrc'." 204 | }, 205 | "LuaCoderAssist.luacheck.keepAfterClosed": { 206 | "type": "boolean", 207 | "default": true, 208 | "description": "Keep diagnostic information after document closed." 209 | }, 210 | "LuaCoderAssist.format.lineWidth": { 211 | "type": "integer", 212 | "default": 120, 213 | "description": "Max character in one line." 214 | }, 215 | "LuaCoderAssist.format.indentCount": { 216 | "type": "integer", 217 | "default": 4, 218 | "description": "Indent count." 219 | }, 220 | "LuaCoderAssist.format.quotemark": { 221 | "type": "string", 222 | "default": "single", 223 | "description": "String quotation style, can be 'single' or 'double'." 224 | }, 225 | "LuaCoderAssist.ldoc.authorInFunctionLevel": { 226 | "type": "boolean", 227 | "default": true, 228 | "description": "Whether @author add to function level document." 229 | }, 230 | "LuaCoderAssist.ldoc.authorName": { 231 | "type": "string", 232 | "default": "", 233 | "description": "The author name." 234 | }, 235 | "LuaCoderAssist.metric.enable": { 236 | "type": "boolean", 237 | "default": true, 238 | "description": "Enable/Disable the code metric codeLens." 239 | }, 240 | "LuaCoderAssist.metric.logicalLineMax": { 241 | "type": "integer", 242 | "default": 50, 243 | "description": "The max logical line of a function." 244 | }, 245 | "LuaCoderAssist.metric.physicalLineMax": { 246 | "type": "integer", 247 | "default": 80, 248 | "description": "The max physical line of a function." 249 | }, 250 | "LuaCoderAssist.metric.cyclomaticMax": { 251 | "type": "integer", 252 | "default": 10, 253 | "description": "The max complexity of a function." 254 | }, 255 | "LuaCoderAssist.metric.maintainabilityMin": { 256 | "type": "integer", 257 | "default": 60, 258 | "description": "The minimum maintainability index of a function." 259 | }, 260 | "LuaCoderAssist.completion.autoInsertParameters": { 261 | "type": "boolean", 262 | "default": true, 263 | "description": "Insert function parameter list automatically." 264 | } 265 | } 266 | }, 267 | "snippets": [ 268 | { 269 | "language": "lua", 270 | "path": "./snippets/ldoc.json" 271 | }, 272 | { 273 | "language": "lua", 274 | "path": "./snippets/lua.json" 275 | } 276 | ] 277 | }, 278 | "scripts": { 279 | "postinstall": "node ./node_modules/vscode/bin/install", 280 | "test": "node ./node_modules/vscode/bin/test" 281 | }, 282 | "bin": { 283 | "lua-coder-assist": "server/server-stdio.js" 284 | }, 285 | "dependencies": { 286 | "autodetect-decoder-stream": "^1.0.3", 287 | "check-types": "~4", 288 | "escomplex": "0.2.5", 289 | "escomplex-traits": "0.2.x", 290 | "find-up": "^4.1.0", 291 | "lodash": "^4.17.15", 292 | "lua-fmt": "^2.6.0", 293 | "luaparse": "^0.2.1", 294 | "opn": "^5.5.0", 295 | "underscore": "^1.9.1", 296 | "vscode-languageclient": "^5.2.1", 297 | "vscode-languageserver": "^5.2.1", 298 | "vscode-uri": "^1.0.8", 299 | "walk": "^2.3.9" 300 | }, 301 | "devDependencies": { 302 | "@types/mocha": "^2.2.48", 303 | "@types/node": "^6.14.9", 304 | "eslint": "^3.6.0", 305 | "mocha": "^2.3.3", 306 | "typescript": "^2.9.2", 307 | "vscode": "^1.1.36" 308 | }, 309 | "homepage": "https://github.com/liwangqian/LuaCoderAssist/blob/master/README.md", 310 | "bugs": { 311 | "url": "https://github.com/liwangqian/LuaCoderAssist/issues", 312 | "email": "liwangqian87@163.com" 313 | }, 314 | "license": "MIT", 315 | "repository": { 316 | "type": "git", 317 | "url": "https://github.com/liwangqian/LuaCoderAssist" 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /server/coder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const langserver_1 = require('vscode-languageserver'); 4 | const uri_1 = require('vscode-uri').default; 5 | const tracer_1 = require('./tracer'); 6 | const preload_1 = require('./preload'); 7 | const awaiter = require('./providers/lib/awaiter'); 8 | const file_manager_1 = require('./providers/lib/file-manager'); 9 | const symbol_provider_1 = require('./providers/symbol-provider'); 10 | const definition_provider_1 = require('./providers/definition-provider'); 11 | const completion_provider_1 = require('./providers/completion-provider'); 12 | const diagnostic_provider_1 = require('./providers/diagnostic-provider'); 13 | const hover_provider_1 = require('./providers/hover-provider'); 14 | const signature_provider = require('./providers/signature-provider'); 15 | const rename_provider = require('./providers/rename-provider'); 16 | const format_provider = require('./providers/format-provider'); 17 | const ldoc_provider = require('./providers/ldoc-provider'); 18 | const engine = require('./lib/engine'); 19 | const busted = require('./lib/busted'); 20 | const adds = require('autodetect-decoder-stream'); 21 | const fs = require('fs'); 22 | 23 | class Coder { 24 | constructor() { 25 | this.workspaceRoot = undefined; 26 | this.workspaceFolders = undefined; 27 | this.conn = undefined; 28 | this.documents = undefined; 29 | this.settings = { 30 | enable: true, 31 | debug: false, 32 | preloads:[], 33 | useLove: false, 34 | useJit: false, 35 | symbol: { 36 | showAnonymousFunction: true, 37 | showFunctionOnly: false, 38 | }, 39 | luaPath:"", 40 | luacheck: { 41 | enable: true, 42 | onSave: true, 43 | onTyping: true, 44 | execPath: null, 45 | std: ["lua51", "busted"], 46 | ignore: [], 47 | jobs: 1, 48 | fileSizeLimit: 100, 49 | maxProblems: 250, 50 | configFilePath: "", 51 | keepAfterClosed: true 52 | }, 53 | search: { 54 | filters: [], 55 | externalPaths: [], 56 | followLinks: false 57 | }, 58 | luaparse: { 59 | luaversion: "5.1", 60 | allowDefined: false 61 | }, 62 | format: { 63 | lineWidth: 120, 64 | indentCount: 4, 65 | quotemark: "single" 66 | }, 67 | ldoc: { 68 | authorInFunctionLevel: true, 69 | authorName: "", 70 | }, 71 | metric: { 72 | enable: true, 73 | logicalLineMax: 50, 74 | physicalLineMax: 80, 75 | cyclomaticMax: 10, 76 | maintainabilityMin: 60 77 | }, 78 | completion: { 79 | autoInsertParameters: true 80 | } 81 | }; 82 | this.tracer = tracer_1.instance(); 83 | this.extensionPath = __dirname + '/../'; 84 | this.luaversion = '5.1'; 85 | 86 | this._initialized = false; 87 | } 88 | 89 | init(context) { 90 | if (this._initialized) { 91 | return true; 92 | } 93 | 94 | if (!context || !context.connection || !context.documents) { 95 | return false; 96 | } 97 | 98 | this.workspaceFolders = context.workspaceFolders.map(folder => { 99 | return uri_1.parse(folder.uri).fsPath; 100 | }); 101 | this.workspaceRoot = context.workspaceRoot; 102 | this.conn = context.connection; 103 | this.documents = context.documents; 104 | this.exdocuments = new Map(); 105 | this._initialized = true; 106 | 107 | this.tracer.init(this); 108 | this._symbolProvider = new symbol_provider_1.SymbolProvider(this); 109 | this._definitionProvider = new definition_provider_1.DefinitionProvider(this); 110 | this._completionProvider = new completion_provider_1.CompletionProvider(this); 111 | this._diagnosticProvider = new diagnostic_provider_1.DiagnosticProvider(this); 112 | this._hoverProvider = new hover_provider_1.HoverProvider(this); 113 | this._signatureProvider = new signature_provider.SignatureProvider(this); 114 | this._renameProvider = new rename_provider.RenameProvider(this); 115 | this._formatProvider = new format_provider.FormatProvider(this); 116 | this._ldocProvider = new ldoc_provider.LDocProvider(this); 117 | 118 | this.conn.console.info('coder inited'); 119 | 120 | return true; 121 | } 122 | 123 | initialized() { 124 | return this._initialized; 125 | } 126 | 127 | document(uri) { 128 | return awaiter.await(this, void 0, void 0, function* () { 129 | let document = this.documents.get(uri); 130 | if (document) { 131 | return document; 132 | } 133 | 134 | document = this.exdocuments.get(uri) 135 | if (document) { 136 | return document; 137 | } 138 | 139 | let _this = this; 140 | return new Promise((resolve) => { 141 | let fileName = uri_1.parse(uri).fsPath; 142 | let stream = fs.createReadStream(fileName).pipe(new adds()); 143 | stream.collect((error, content) => { 144 | document = langserver_1.TextDocument.create(uri, "lua", 0, content); 145 | _this.exdocuments.set(uri, document); 146 | resolve(document); 147 | }); 148 | }); 149 | }); 150 | } 151 | 152 | onDidChangeConfiguration(change) { 153 | if (!change.settings || !change.settings.LuaCoderAssist) { 154 | return; 155 | } 156 | 157 | let settings = change.settings.LuaCoderAssist; 158 | this.settings = settings; 159 | this.luaversion = settings.luaparse.luaversion; 160 | let fileManager = file_manager_1.instance(); 161 | fileManager.reset(); 162 | 163 | const workspaceFolders = this.workspaceFolders || (this.workspaceRoot && [this.workspaceRoot]); 164 | if (workspaceFolders) { 165 | fileManager.setRoots(settings.search.externalPaths.concat(...workspaceFolders)); 166 | workspaceFolders.forEach(folder => { 167 | fileManager.addLuaPath(settings.luaPath.replace(/\{workspaceRoot\}/g, folder)); 168 | }); 169 | } 170 | 171 | preload_1.loadAll(this); 172 | fileManager.searchFiles(settings.search, ".lua"); 173 | } 174 | 175 | onDidChangeContent(change) { 176 | let uri = change.document.uri; 177 | const mdl = engine.parseDocument(change.document.getText(), uri, this.tracer); 178 | if (mdl) { 179 | setTimeout(parseDependences, 0, mdl, this); 180 | } 181 | 182 | if (this.settings.luacheck.onTyping) { 183 | this._diagnosticProvider.provideDiagnostics(uri); 184 | } 185 | } 186 | 187 | onDidSave(params) { 188 | if (this.settings.luacheck.onSave) { 189 | this._diagnosticProvider.provideDiagnostics(params.document.uri); 190 | } 191 | } 192 | 193 | onDidChangeWatchedFiles(change) { 194 | change.changes.forEach(event => { 195 | let uri = event.uri; 196 | let etype = event.type; 197 | let filePath = uri_1.parse(uri).fsPath; 198 | let fileManager = file_manager_1.instance(); 199 | switch (etype) { 200 | case 1: //create 201 | fileManager.addFile(filePath); 202 | break; 203 | case 3: //delete 204 | fileManager.delFile(filePath); 205 | let _package = engine.LoadedPackages[uri]; 206 | if (_package) { 207 | _package.invalidate(); 208 | delete engine.LoadedPackages[uri]; 209 | engine.utils.invalidateModuleSymbols(uri); 210 | engine.utils.clearInvalidSymbols(_package.type.moduleMode, _package.name); 211 | } 212 | break; 213 | default: 214 | break; 215 | } 216 | }); 217 | } 218 | 219 | provideDocumentSymbols(params) { 220 | var uri = params.textDocument.uri; 221 | return this._symbolProvider.provideDocumentSymbols(uri); 222 | } 223 | 224 | provideDefinitions(params) { 225 | // 针对刚开始打开文件时,工程目录下的文件还没找出来,导致无法提供符号跳转 226 | setTimeout(parseDependences, 0, engine.LoadedPackages[params.textDocument.uri], this); 227 | return this._definitionProvider.provideDefinitions(params); 228 | } 229 | 230 | provideCompletions(params) { 231 | return this._completionProvider.provideCompletions(params); 232 | } 233 | 234 | resolveCompletion(item) { 235 | return this._completionProvider.resolveCompletion(item); 236 | } 237 | 238 | provideHover(params) { 239 | setTimeout(parseDependences, 0, engine.LoadedPackages[params.textDocument.uri], this); 240 | return this._hoverProvider.provideHover(params); 241 | } 242 | 243 | provideSignatureHelp(params) { 244 | return this._signatureProvider.provideSignatureHelp(params); 245 | } 246 | 247 | provideRename(params) { 248 | return this._renameProvider.provideRename(params); 249 | } 250 | 251 | formatDocument(params) { 252 | return this._formatProvider.formatRangeText(params); 253 | } 254 | 255 | formatOnTyping(params) { 256 | return this._formatProvider.formatOnTyping(params); 257 | } 258 | 259 | sendDiagnostics(uri, diagnostics) { 260 | this.conn.sendDiagnostics({ 261 | uri: uri, 262 | diagnostics: diagnostics 263 | }); 264 | } 265 | 266 | showWarningMessage(msg) { 267 | this.conn.window.showWarningMessage(msg); 268 | } 269 | 270 | onLDocRequest(params) { 271 | return this._ldocProvider.onRequest(params); 272 | } 273 | 274 | onBustedRequest(params) { 275 | if (params === true) { 276 | busted.enterBustedMode(this.extensionPath); 277 | } else { 278 | busted.exitBustedMode(); 279 | } 280 | return true; 281 | } 282 | 283 | onDidClosed(doc) { 284 | if (!this.settings.luacheck.keepAfterClosed) { 285 | this.sendDiagnostics(doc.uri, []); 286 | } 287 | } 288 | }; 289 | 290 | var _coderInstance = undefined; 291 | /** 292 | * @returns {Coder} 293 | */ 294 | function instance() { 295 | if (_coderInstance === undefined) { 296 | _coderInstance = new Coder(); 297 | } 298 | 299 | return _coderInstance; 300 | } 301 | 302 | function parseDependences(mdl, coder) { 303 | if (!mdl) { 304 | return; 305 | } 306 | 307 | const fileManager = file_manager_1.instance(); 308 | mdl.type.imports.forEach(dep => { 309 | const files = fileManager.getFiles(dep.name); 310 | files.forEach(file => { 311 | awaiter.await(void 0, void 0, void 0, function* () { 312 | const uri = uri_1.file(file).toString(); 313 | if (engine.LoadedPackages[uri]) { 314 | return; 315 | } 316 | const doc = yield coder.document(uri); 317 | engine.parseDocument(doc.getText(), uri, coder.tracer); 318 | }); 319 | }); 320 | }); 321 | } 322 | 323 | exports.instance = instance; 324 | -------------------------------------------------------------------------------- /server/lib/busted.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const engine = require('./engine'); 4 | 5 | const BUSTED_PRELOAD = 'stdlibs/busted.json'; 6 | 7 | let _stdAssert = undefined; 8 | let _bustedMode = false; 9 | 10 | function enterBustedMode(extensionPath) { 11 | _stdAssert = engine._G.get('assert'); 12 | engine._G.set('assert', undefined, true); 13 | const busted = engine.LoadedPackages['busted']; 14 | if (busted) { 15 | busted.state.valid = true; 16 | Object.assign(engine._G.type._fields, busted.type._fields); 17 | } else { 18 | engine.loadExtentLib(extensionPath + BUSTED_PRELOAD, "busted"); 19 | } 20 | _bustedMode = true; 21 | } 22 | 23 | exports.enterBustedMode = enterBustedMode; 24 | 25 | function exitBustedMode() { 26 | if (!_bustedMode) { 27 | return; 28 | } 29 | const busted = engine.LoadedPackages['busted']; 30 | busted.state.valid = false; 31 | clearInvalidSymbols(); 32 | engine._G.set('assert', _stdAssert, true); 33 | } 34 | 35 | function clearInvalidSymbols() { 36 | let globals = engine._G.type.fields; 37 | for (const name in globals) { 38 | if (!globals[name].valid) { 39 | delete globals[name]; 40 | } 41 | } 42 | } 43 | 44 | exports.exitBustedMode = exitBustedMode; 45 | -------------------------------------------------------------------------------- /server/lib/engine/analysis.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { LoadedPackages } = require('./luaenv'); 19 | const { invalidateModuleSymbols, clearInvalidSymbols } = require('./utils'); 20 | const { analysis } = require('./core'); 21 | 22 | function addLoadedPackage(name, pkg) { 23 | // 用于方便查找定义 24 | LoadedPackages[pkg.uri] = pkg; 25 | let pkgs = LoadedPackages[name]; 26 | if (!pkgs) { 27 | pkgs = {}; 28 | LoadedPackages[name] = pkgs; 29 | } 30 | pkgs[pkg.uri] = pkg; 31 | } 32 | 33 | const MODULE_NAME_REGEX = /(\w+)?[\\\\|/]?init\.lua/; 34 | function dumpPackageWithInit(pkg) { 35 | const matches = MODULE_NAME_REGEX.exec(pkg.uri); 36 | if (matches) { 37 | addLoadedPackage(matches[1], pkg); 38 | } 39 | } 40 | 41 | function parseDocument(code, uri, logger) { 42 | try { 43 | invalidateModuleSymbols(uri); 44 | let pkg = analysis(code, uri); 45 | addLoadedPackage(pkg.name, pkg); 46 | dumpPackageWithInit(pkg); 47 | clearInvalidSymbols(pkg.type.moduleMode, pkg.name); 48 | return pkg; 49 | } catch (err) { 50 | if (!err.stack.includes('luaparse.js')) { 51 | logger.error(err.stack); 52 | } 53 | return null; 54 | } 55 | } 56 | 57 | module.exports = { 58 | parseDocument 59 | }; 60 | -------------------------------------------------------------------------------- /server/lib/engine/completion.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { LoadedPackages, _G } = require('./luaenv'); 19 | const { object2Array } = require('./utils'); 20 | const { typeOf, findDef, searchInnerStackIndex } = require('./typeof'); 21 | const { ScopeEnd } = require('./linear-stack'); 22 | const Is = require('./is'); 23 | 24 | class CompletionContext { 25 | /** 26 | * 27 | * @param {String} expr completion expression, eg. `abc.def:hg()` => `abc:def:gh` 28 | * @param {Array} range 29 | * @param {String} uri 30 | */ 31 | constructor(expr, range, uri) { 32 | this.expr = expr; 33 | this.names = expr.trim().split(/[\.\:]/); 34 | this.range = range; 35 | this.uri = uri; 36 | this.isString = false; 37 | this.functionOnly = expr.lastIndexOf(':') > expr.lastIndexOf('.'); 38 | } 39 | }; 40 | 41 | 42 | let completionMapCache = new Map(); 43 | /** 44 | * Provide completion items 45 | * @param {CompletionContext} context 46 | */ 47 | function completionProvider(context) { 48 | const namesLength = context.names.length; 49 | let theModule = LoadedPackages[context.uri]; 50 | if (!theModule || namesLength === 0) { 51 | return []; 52 | } 53 | 54 | let stack = theModule.type.menv.stack; 55 | let index = searchInnerStackIndex(stack, context.range); 56 | let skipNode = (node) => ScopeEnd.is(node); 57 | 58 | //Case: abc 59 | if (namesLength === 1) { 60 | completionMapCache = new Map(); 61 | let node = stack.nodes[index - 1]; 62 | while (node) { 63 | const name = node.data.name; 64 | !skipNode(node) && !completionMapCache.has(name) && completionMapCache.set(name, node.data); 65 | node = node.prev; 66 | }; 67 | 68 | theModule.type.walk(fields => { 69 | for (const name in fields) { 70 | const symbol = fields[name]; 71 | !completionMapCache.has(name) && completionMapCache.set(name, symbol); 72 | } 73 | }); 74 | 75 | let symbolArray = []; 76 | completionMapCache.forEach(value => { 77 | symbolArray.push(value); 78 | }); 79 | 80 | return symbolArray; 81 | } 82 | 83 | //Case: abc.x or abc.xy:z ... 84 | //TODO: support abc().xx 85 | const name = context.names[0]; 86 | let value = completionMapCache.get(name); 87 | if (!value) { 88 | value = findDef(name, context.uri, context.range); 89 | } 90 | 91 | if (name.length === 0 && context.isString) { 92 | value = _G.get('string'); 93 | } 94 | 95 | if (!value) { 96 | return []; 97 | } 98 | 99 | if (Is.luaFunction(typeOf(value))) { 100 | if (!value.type.returns) { 101 | return []; 102 | } 103 | value = value.type.returns[0]; 104 | } 105 | 106 | if (!value) { 107 | return []; 108 | } 109 | 110 | if (Is.luaString(value.type)) { 111 | value = _G.get('string'); 112 | } 113 | 114 | if (!Is.luaTable(typeOf(value)) && !Is.luaModule(value)) { 115 | return []; 116 | } 117 | 118 | let def = value; 119 | const size = namesLength - 1; 120 | for (let i = 1; i < size; ++i) { 121 | let name = context.names[i]; 122 | def = def.type.search(name, context.range).value; 123 | if (!def) { 124 | return []; 125 | } 126 | 127 | let type = typeOf(def); 128 | 129 | // func().abc 130 | if (Is.luaFunction(type)) { 131 | if (!type.returns) { 132 | return []; 133 | } 134 | def = type.returns[0]; 135 | type = typeOf(def); 136 | } 137 | 138 | if (Is.luaString(type) && _G.get('string')) { 139 | def = _G.get('string'); 140 | type = def.type; 141 | } 142 | 143 | if (Is.luaTable(type)) { 144 | continue; 145 | } else { 146 | return []; 147 | } 148 | } 149 | 150 | const filter = item => context.functionOnly && !Is.luaFunction(item.type); 151 | let children 152 | if (Is.luaModule(def.type)) { 153 | children = def.type.fields; 154 | } else { 155 | children = Object.create(null); 156 | def.type.walk(fields => { 157 | Object.assign(children, fields); 158 | }); 159 | } 160 | 161 | return object2Array(def.type.return || children, filter); 162 | } 163 | 164 | module.exports = { 165 | CompletionContext, completionProvider 166 | } 167 | -------------------------------------------------------------------------------- /server/lib/engine/definition.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { LoadedPackages } = require('./luaenv'); 19 | const { typeOf, findDef } = require('./typeof'); 20 | const Is = require('./is'); 21 | 22 | class DefinitionContext { 23 | /** 24 | * Context for query definition of a reference 25 | * @param {String} expr refernce names, eg. `base.func()` => `base.func` 26 | * @param {Array} range refernce range, include the base names 27 | * @param {String} uri uri of the document where reference exist 28 | */ 29 | constructor(expr, range, uri) { 30 | this.names = expr.trim().split(/[\.\:]/); 31 | this.range = range; 32 | this.uri = uri; 33 | } 34 | }; 35 | 36 | 37 | /** 38 | * Provide the definition of the reference 39 | * @param {DefinitionContext} context query context 40 | */ 41 | function definitionProvider(context) { 42 | const names = context.names; 43 | const length = names.length; 44 | let def = findDef(names[0], context.uri, context.range); 45 | if (!def) { 46 | //查找已加载的模块列表 47 | let defs = []; 48 | let loaded = LoadedPackages[names[length-1]]; 49 | if (loaded) { 50 | for (const loadedUri in loaded) { 51 | defs.push(loaded[loadedUri]); 52 | } 53 | } 54 | return defs; 55 | } 56 | 57 | let type = typeOf(def); 58 | if (length === 1) { 59 | return [def]; 60 | } 61 | 62 | if (Is.luaFunction(type)) { 63 | if (!def.type.returns) { 64 | return []; 65 | } 66 | def = def.type.returns[0]; 67 | } 68 | 69 | if (Is.lazyValue(type)) { 70 | type = typeOf(def); //try deduce type 71 | } 72 | 73 | if (!Is.luaTable(type) && !Is.luaModule(type)) { 74 | return []; 75 | } 76 | 77 | for (let i = 1; i < (length - 1); i++) { 78 | const name = names[i]; 79 | def = def.type.search(name, context.range).value; 80 | if (!def) { 81 | return []; 82 | } 83 | 84 | let type = typeOf(def); 85 | 86 | // func().abc 87 | if (Is.luaFunction(type)) { 88 | if (!type.returns) { 89 | return []; 90 | } 91 | def = type.returns[0]; 92 | type = typeOf(def); 93 | } 94 | 95 | if (Is.luaTable(type)) { 96 | continue; 97 | } else { 98 | return []; 99 | } 100 | } 101 | 102 | def = def.type.search(names[length - 1], context.range).value; 103 | if (def) { 104 | return [def]; 105 | } 106 | 107 | return []; 108 | } 109 | 110 | module.exports = { 111 | DefinitionContext, definitionProvider 112 | }; 113 | -------------------------------------------------------------------------------- /server/lib/engine/extend.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { 19 | LuaModule, 20 | LuaTable, 21 | LuaFunction, 22 | LuaBasicTypes, 23 | LuaSymbol, 24 | LuaSymbolKind, 25 | lazyType, 26 | LuaNameType 27 | } = require('./symbol'); 28 | const { namedTypes, _G, LoadedPackages } = require('./luaenv'); 29 | const fs_1 = require('fs'); 30 | 31 | let state = undefined; 32 | 33 | function loadExtentLib(filePath, moduleName, logger) { 34 | fs_1.readFile(filePath, 'utf8', (error, data) => { 35 | if (!error) { 36 | let lib = undefined; 37 | try { 38 | lib = JSON.parse(data); 39 | state = undefined; /*namedTypes不需要state*/ 40 | parseNamedTypes(lib); 41 | parseModule(lib, moduleName); 42 | } catch (e) { 43 | logger && logger.error(e.message); 44 | } 45 | } else { 46 | logger && logger.error(error.message); 47 | } 48 | }); 49 | } 50 | 51 | exports.loadExtentLib = loadExtentLib; 52 | 53 | const newExtentSymbol = (name, isLocal, kind, type) => { 54 | let newSymbol = new LuaSymbol(name, null, null, null, isLocal, null, kind, type); 55 | newSymbol.state = state; 56 | return newSymbol; 57 | } 58 | 59 | function parseNamedTypes(json) { 60 | if (!json || !json.namedTypes) { 61 | return; 62 | } 63 | 64 | const types = json.namedTypes; 65 | if (!(types instanceof Object)) { 66 | return; 67 | } 68 | 69 | for (const name in types) { 70 | const value = types[name]; 71 | const symbol = parseJsonObject(value, name); 72 | symbol.type.typeName = name; 73 | symbol && namedTypes.set(name, symbol); 74 | } 75 | } 76 | 77 | function parseModule(json, moduleName) { 78 | if (!json || !json.global) { 79 | return; 80 | } 81 | 82 | const global = json.global; 83 | if (global.type !== 'table') { 84 | return; 85 | } 86 | 87 | const fields = global.fields; 88 | if (!(fields instanceof Object)) { 89 | return; 90 | } 91 | 92 | state = { valid: true }; /*每个外部lib使用独立的state*/ 93 | let theModule; 94 | if (moduleName) { 95 | /*创建一个module来保存外部lib符号*/ 96 | theModule = newExtentSymbol(moduleName, false, LuaSymbolKind.module, new LuaModule()); 97 | theModule.state = state; 98 | } 99 | 100 | for (const name in fields) { 101 | const value = fields[name]; 102 | const symbol = parseJsonObject(value, name); 103 | if (symbol) { 104 | let existTbl; 105 | if (symbol.type instanceof LuaTable) { 106 | existTbl = _G.get(name); 107 | } 108 | if (existTbl) { 109 | /** 110 | * 合并,支持扩展自带的std\love\jit库 111 | */ 112 | if (existTbl.type instanceof LuaTable) { 113 | Object.assign(existTbl.type.fields, symbol.type.fields); 114 | } 115 | } else { 116 | _G.set(name, symbol); 117 | } 118 | 119 | theModule && theModule.set(name, symbol); 120 | } 121 | } 122 | 123 | if (theModule) { 124 | /*保存module起来用于支持动态加载和卸载*/ 125 | LoadedPackages[moduleName] = theModule; 126 | } 127 | } 128 | 129 | /** 130 | * Parse the json format interface desc data. 131 | * @param {*} node JSON object 132 | * @param {String} name Name of the node 133 | * 134 | * @returns {LuaSymbol} 135 | */ 136 | function parseJsonObject(node, name) { 137 | if (!node) { 138 | return undefined; 139 | } 140 | 141 | if (!name && (node instanceof Object)) { 142 | const object = {}; 143 | for (const key in node) { 144 | const value = node[key]; 145 | const symbol = parseJsonObject(value, key); 146 | symbol && (object[key] = symbol); 147 | } 148 | return object; 149 | } 150 | 151 | switch (node.type) { 152 | case 'table': 153 | return parseTableJsonObject(node, name); 154 | case 'function': 155 | return parseFunctionJsonObject(node, name); 156 | case 'ref': 157 | return parseRefJsonObject(node, name); 158 | case 'string': 159 | case 'number': 160 | case 'boolean': 161 | return parseLuaBasicTypeJsonObject(node, name); 162 | default: 163 | return newExtentSymbol(name, false, LuaSymbolKind.variable, new LuaNameType(node.type)); 164 | } 165 | } 166 | 167 | function parseTableJsonObject(node, name) { 168 | let table = new LuaTable(); 169 | table._fields = parseJsonObject(node.fields) || {}; 170 | table._metatable = parseJsonObject(node.metatable, '__mt'); 171 | table.description = node.description; 172 | table.link = node.link; 173 | let symbol = newExtentSymbol(name, false, LuaSymbolKind.table, table); 174 | return symbol; 175 | } 176 | 177 | function parseFunctionJsonObject(node, name) { 178 | let func = new LuaFunction(); 179 | func.description = node.description; 180 | func.link = node.link; 181 | func.insertSnippet = node.insertSnippet; 182 | func.isConstructor = (node.kind === "constructor") ? true : false; 183 | func.args = parseArgumentsObject(node.args); 184 | func.returns = parseReturnsObject(node.returnTypes); 185 | parseVariantsArgumentsObject(node.variants, func); 186 | let symbol = newExtentSymbol(name, false, LuaSymbolKind.function, func); 187 | return symbol; 188 | } 189 | 190 | function parseVariantsArgumentsObject(variants, func) { 191 | if (!variants) { 192 | return; 193 | } 194 | 195 | func.variants = variants.map(variant => { 196 | return { 197 | description: variant.description || '', 198 | args: parseArgumentsObject(variant.args), 199 | returns: parseReturnsObject(variant.returnTypes) 200 | }; 201 | }); 202 | } 203 | 204 | function parseArgumentsObject(args) { 205 | if (!args) { 206 | return []; 207 | } 208 | return args.map(arg => { 209 | const symbol = newExtentSymbol(arg.name, true, LuaSymbolKind.parameter, LuaBasicTypes.any); 210 | symbol.displayName = arg.displayName; 211 | return symbol; 212 | }); 213 | } 214 | 215 | function parseReturnsObject(returns) { 216 | if (!returns) { 217 | return undefined; 218 | } 219 | return returns.map((rt, index) => { 220 | return parseJsonObject(rt, rt.name || `R${index}`); 221 | }); 222 | } 223 | 224 | function parseLuaBasicTypeJsonObject(node, name) { 225 | const type = LuaBasicTypes[node.type]; 226 | const symbol = newExtentSymbol(name, false, LuaSymbolKind.variable, type); 227 | return symbol; 228 | } 229 | 230 | function parseRefJsonObject(node, name) { 231 | const type = lazyType(null, node, name, 0); //delay evaluate 232 | const symbol = newExtentSymbol(name, false, LuaSymbolKind.variable, type); 233 | return symbol; 234 | } 235 | -------------------------------------------------------------------------------- /server/lib/engine/index.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { typeOf } = require('./typeof'); 19 | const { LoadedPackages, _G } = require('./luaenv'); 20 | const { parseDocument } = require('./analysis'); 21 | const { CompletionContext, completionProvider } = require('./completion'); 22 | const { DefinitionContext, definitionProvider } = require('./definition'); 23 | const { loadExtentLib } = require('./extend'); 24 | const is = require('./is'); 25 | const utils = require('./utils'); 26 | 27 | module.exports = { 28 | typeOf, parseDocument, CompletionContext, completionProvider, DefinitionContext, definitionProvider, LoadedPackages, _G, loadExtentLib, is, utils 29 | }; -------------------------------------------------------------------------------- /server/lib/engine/is.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { 19 | LuaTable, 20 | LuaFunction, 21 | LuaModule, 22 | LuaBasicTypes, 23 | LazyValue 24 | } = require('./symbol'); 25 | 26 | function luaTable(t) { 27 | return t instanceof LuaTable; 28 | } 29 | 30 | function luaFunction(t) { 31 | return t instanceof LuaFunction; 32 | } 33 | 34 | function luaModule(t) { 35 | return t instanceof LuaModule; 36 | } 37 | 38 | function luaString(t) { 39 | return t === LuaBasicTypes.string; 40 | } 41 | 42 | function luaBoolean(t) { 43 | return t === LuaBasicTypes.boolean; 44 | } 45 | 46 | function luaNumber(t) { 47 | return t === LuaBasicTypes.number; 48 | } 49 | 50 | function luaAny(t) { 51 | return t === LuaBasicTypes.any; 52 | } 53 | 54 | function lazyValue(t) { 55 | return t instanceof LazyValue; 56 | } 57 | 58 | module.exports = { 59 | luaFunction, 60 | luaModule, 61 | luaTable, 62 | luaString, 63 | luaNumber, 64 | luaBoolean, 65 | luaAny, 66 | lazyValue 67 | }; 68 | -------------------------------------------------------------------------------- /server/lib/engine/linear-stack.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | class StackNode { 19 | constructor(data) { 20 | this.data = data; 21 | this._prevNode = null; 22 | } 23 | 24 | get prev() { 25 | return this._prevNode; 26 | } 27 | 28 | set prev(_prevNode) { 29 | this._prevNode = _prevNode; 30 | } 31 | }; 32 | 33 | class ScopeEnd extends StackNode { 34 | /** 35 | * @param {Number[]} location 36 | */ 37 | constructor(location) { 38 | super({ location, range: location }); 39 | } 40 | 41 | static is(node) { 42 | return node instanceof ScopeEnd; 43 | } 44 | }; 45 | 46 | class LinearStack { 47 | constructor() { 48 | this.nodes = []; 49 | } 50 | 51 | /** 52 | * Push node on top of the stack 53 | * @param {StackNode} node 54 | */ 55 | push(node) { 56 | this.nodes.push(node); 57 | } 58 | 59 | /** 60 | * Pop the top node and return. 61 | * @returns {StackNode} the top node 62 | */ 63 | pop() { 64 | return this.nodes.pop(); 65 | } 66 | 67 | /** 68 | * Fetch the top node of the stack. 69 | * @returns {StackNode} The top node of the stack. 70 | */ 71 | top() { 72 | return this.nodes[this.length() - 1]; 73 | } 74 | 75 | length() { 76 | return this.nodes.length; 77 | } 78 | 79 | /** 80 | * Search the stack down 81 | * @param {Function} predicate The predicate use for node match. 82 | * @param {Number} fromIndex The position to start search. 83 | */ 84 | search(predicate, fromIndex) { 85 | if (fromIndex == null) { 86 | fromIndex = this.length(); 87 | } 88 | fromIndex -= 1; 89 | let node = this.nodes[fromIndex]; 90 | let skip = (n) => ScopeEnd.is(n); 91 | 92 | while (node) { 93 | if (!skip(node) && predicate(node.data)) { 94 | return node.data; 95 | } 96 | node = node.prev; 97 | } 98 | 99 | return null; 100 | } 101 | 102 | /** 103 | * Walk the stack from top to bottom, Not all elements will touch. 104 | * @param {Function} callback The callback function 105 | * @param {Number} fromIndex The begin index to walk 106 | */ 107 | walk(callback, fromIndex) { 108 | fromIndex = fromIndex || (this.length() - 1); 109 | let node = this.nodes[fromIndex]; 110 | let skip = n => ScopeEnd.is(n); 111 | while (node) { 112 | if (!skip(node)) { 113 | callback(node.data); 114 | } 115 | 116 | node = node.prev; 117 | } 118 | } 119 | 120 | /** 121 | * Walk all the DATA node in the stack. 122 | * ScopeEnd tag element will be skipped. 123 | * @param {Function} callback The callback function invoked for every element. 124 | */ 125 | forEach(callback) { 126 | let skip = n => ScopeEnd.is(n); 127 | this.nodes.forEach(node => { 128 | if (skip(node)) { 129 | return; 130 | } 131 | callback(node.data); 132 | }); 133 | } 134 | } 135 | 136 | class Scope { 137 | /** 138 | * @param {LinearStack} stack 139 | */ 140 | constructor(stack, range) { 141 | this.stack = stack; 142 | this.range = range; 143 | } 144 | 145 | push(data) { 146 | let node = new StackNode(data); 147 | node.prev = this.prev; 148 | this.prev = node; 149 | this.stack.push(node); 150 | } 151 | 152 | /** 153 | * Enter a new scope. 154 | * @param {Scope} parent The parent scope. 155 | * @returns {Scope} The new scope. 156 | */ 157 | enter(parent) { 158 | this._parent = parent; 159 | this.prev = parent && parent.prev; 160 | return this; 161 | } 162 | 163 | /** 164 | * Exit the scope and return it's parent scope. 165 | * @returns {Scope} The parent scope 166 | */ 167 | exit(range) { 168 | let E = new ScopeEnd(range); 169 | E.prev = this._parent.prev; 170 | this.stack.push(E); 171 | return this._parent; 172 | } 173 | } 174 | 175 | 176 | exports.Scope = Scope; 177 | exports.ScopeEnd = ScopeEnd; 178 | exports.StackNode = StackNode; 179 | exports.LinearStack = LinearStack; 180 | -------------------------------------------------------------------------------- /server/lib/engine/luaenv.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const { LuaTable, LuaSymbol, LuaSymbolKind } = require('./symbol'); 19 | 20 | const createTableSymbol = (name, loc, scope, local) => { 21 | return new LuaSymbol(name, loc, loc, scope, local, null, LuaSymbolKind.table, new LuaTable()); 22 | } 23 | 24 | const _G = createTableSymbol('_G', [0, 3], [0, Infinity], false); 25 | _G.state = { valid: true }; 26 | _G.set(_G.name, _G); 27 | 28 | const LoadedPackages = {}; 29 | 30 | const global__metatable = createTableSymbol('_G__metatable', [0, 0], [0, Infinity], false); 31 | global__metatable.state = { valid: true }; 32 | global__metatable.set('__index', _G); 33 | 34 | const namedTypes = new Map(); 35 | 36 | module.exports = { 37 | _G, 38 | LoadedPackages, 39 | namedTypes, 40 | global__metatable 41 | } 42 | -------------------------------------------------------------------------------- /server/lib/engine/utils.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2018 The LuaCoderAssist Authors. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ********************************************************************************/ 16 | 'use strict'; 17 | 18 | const is = require('./is'); 19 | const { _G, LoadedPackages } = require('./luaenv'); 20 | 21 | function identName(ident) { 22 | if (ident) { 23 | return ident.name || identName(ident.identifier); 24 | } else { 25 | return null; 26 | } 27 | } 28 | 29 | function baseName(ident) { 30 | if (!ident) { 31 | return null; 32 | } 33 | if (ident.base) { 34 | return baseName(ident.base); 35 | } else { 36 | return ident.name; 37 | } 38 | } 39 | 40 | function baseNames(node) { 41 | let names = []; 42 | const _iter = (base) => { 43 | if (!base) { 44 | return; 45 | } 46 | 47 | if (base.base) { 48 | _iter(base.base); 49 | } 50 | 51 | let name = identName(base); 52 | if (name) { 53 | names.push(identName(base)); 54 | } 55 | } 56 | 57 | _iter(node); 58 | return names; 59 | } 60 | 61 | function safeName(node) { 62 | return node.name || '@(' + node.range + ')'; 63 | } 64 | 65 | function object2Array(obj, filterout) { 66 | let array = []; 67 | filterout = filterout || (() => false); 68 | for (const key in obj) { 69 | const e = obj[key]; 70 | if (!filterout(e)) { 71 | array.push(e); 72 | } 73 | } 74 | return array; 75 | } 76 | 77 | function directParent(stack, names) { 78 | let parent = stack.search((data) => data.name === names[0]); 79 | parent = parent || _G.get(names[0]); 80 | for (let i = 1; i < names.length; i++) { 81 | if (parent && is.luaTable(parent.type)) { 82 | const result = parent.type.search(names[i]); 83 | parent = result.value; 84 | continue; 85 | } else { 86 | parent = null; 87 | break; 88 | } 89 | } 90 | return parent; 91 | } 92 | 93 | function invalidateModuleSymbols(uri) { 94 | const _package = LoadedPackages[uri]; 95 | if (_package) { 96 | _package.invalidate(); 97 | } 98 | } 99 | 100 | function clearInvalidSymbols(moduleMode, moduleName) { 101 | if (moduleMode) { 102 | const theModule = _G.type.get(moduleName); 103 | deleteInvalidSymbols(theModule.type.fields); 104 | theModule.state.valid = true; 105 | } else { 106 | deleteInvalidSymbols(_G.type.fields) 107 | } 108 | } 109 | 110 | function deleteInvalidSymbols(table) { 111 | for (const name in table) { 112 | if (!table[name].valid) { 113 | delete table[name]; 114 | } 115 | } 116 | } 117 | 118 | module.exports = { 119 | identName, 120 | baseName, 121 | baseNames, 122 | safeName, 123 | directParent, 124 | object2Array, 125 | invalidateModuleSymbols, 126 | clearInvalidSymbols 127 | }; 128 | -------------------------------------------------------------------------------- /server/lib/symbol/scope.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Scope { 4 | constructor() { 5 | this.definitions = {}; 6 | } 7 | 8 | valueOf(name) { 9 | return this.definitions[name]; 10 | } 11 | 12 | add(name, isclass) { 13 | if (isclass) { 14 | this.definitions[name] = {}; 15 | } else { 16 | this.definitions[name] = true; 17 | } 18 | } 19 | 20 | addToClass(className, property, isclass) { 21 | let aclass = this.definitions[className]; 22 | if (isclass) { 23 | aclass[property] = {}; 24 | } else { 25 | aclass[property] = true; 26 | } 27 | } 28 | }; 29 | 30 | exports.Scope = Scope; -------------------------------------------------------------------------------- /server/lib/symbol/stack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const scope_1 = require('./scope'); 4 | 5 | class Stack { 6 | constructor() { 7 | this.scopes = []; 8 | } 9 | 10 | enterScope() { 11 | let scope = new scope_1.Scope(); 12 | this.scopes.push(scope); 13 | return scope; 14 | } 15 | 16 | leaveScope() { 17 | this.pop(); 18 | } 19 | 20 | currentScope() { 21 | return this.scopes[this.scopes.length - 1]; 22 | } 23 | 24 | findDef(symbol) { 25 | let currScope = this.currentScope(); 26 | if (currScope.valueOf(symbol.bases[symbol.bases.length-1] || symbol.nameS)) { 27 | 28 | } 29 | } 30 | }; 31 | 32 | exports.Stack = Stack; -------------------------------------------------------------------------------- /server/lib/symbol/symbol-traits.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function symbol(name, kind, islocal) { 4 | return { 5 | name: name, 6 | kind: kind, 7 | islocal: islocal, 8 | uri: undefined, 9 | location: undefined, 10 | scope: undefined, 11 | container: undefined, 12 | bases: [], 13 | alias: undefined, 14 | params: undefined, 15 | returnMode: undefined 16 | }; 17 | } 18 | 19 | exports.symbol = symbol; 20 | 21 | var SymbolKind = []; 22 | SymbolKind[SymbolKind["variable"] = 1] = "variable"; 23 | SymbolKind[SymbolKind["parameter"] = 2] = "parameter"; 24 | SymbolKind[SymbolKind["reference"] = 3] = "reference"; 25 | SymbolKind[SymbolKind["function"] = 4] = "function"; 26 | SymbolKind[SymbolKind["class"] = 5] = "class"; 27 | SymbolKind[SymbolKind["module"] = 6] = "module"; 28 | SymbolKind[SymbolKind["dependence"] = 7] = "dependence"; 29 | SymbolKind[SymbolKind["property"] = 8] = "property"; 30 | SymbolKind[SymbolKind["label"] = 9] = "label"; 31 | 32 | exports.SymbolKind = SymbolKind; 33 | -------------------------------------------------------------------------------- /server/lib/symbol/types/AssignmentStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const traits = require('../symbol-traits'); 3 | const utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | for (let i = 0; i < node.variables.length; i++) { 7 | let variable = node.variables[i]; 8 | walker.walkNode(variable, container, scope, parentSymbol, isDef); 9 | 10 | if (node.init && node.init[i]) { 11 | let init = node.init[i]; 12 | walker.walkNode(init, container, scope, parentSymbol, false); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/BinaryExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var traits = require('../symbol-traits'); 4 | var utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | node.left && walker.walkNode(node.left, container, scope, parentSymbol, isDef); 8 | node.right && walker.walkNode(node.right, container, scope, parentSymbol, isDef); 9 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/CallExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const traits = require('../symbol-traits'); 4 | const utils = require('../utils'); 5 | const path_1 = require('path'); 6 | 7 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 8 | let name = utils.safeName(node.base); 9 | 10 | // parse module and dependence 11 | if (name === 'module') { 12 | parseModule(walker, node, container, scope, parentSymbol); 13 | } else if (name === 'require') { 14 | parseDependence(walker, node, container, scope, parentSymbol); 15 | } else if ((name == 'pcall') && (node.arguments[0].value == 'require')) { 16 | parseDependencePcall(walker, node, container, scope, parentSymbol); 17 | } 18 | 19 | let call = traits.symbol(name, traits.SymbolKind.reference, utils.isLocal(node.base)); 20 | call.container = container; 21 | call.location = utils.getLocation(node.base); 22 | call.bases = utils.extractBases(node.base); 23 | 24 | walker.addRef(call); 25 | utils.parseBase(walker, node.base, container, scope, node, traits); 26 | 27 | node.arguments && walker.walkNodes(node.arguments, container, scope, parentSymbol, false); 28 | node.argument && walker.walkNode(node.argument, container, scope, parentSymbol, false); 29 | } 30 | 31 | function parseModule(walker, node, container, scope, parentSymbol) { 32 | let moduleNode = node.argument || node.arguments[0]; 33 | let moduleName = moduleNode.value; 34 | let module = traits.symbol(moduleName, traits.SymbolKind.module, false); 35 | module.location = utils.getLocation(moduleNode); 36 | module.container = {name: container.name}; 37 | container.name = moduleName; 38 | walker.addMod(module); 39 | } 40 | 41 | function parseDependence(walker, node, container, scope, parentSymbol) { 42 | let moduleNode = node.argument || node.arguments[0]; 43 | if (!moduleNode.value) { 44 | return; 45 | } 46 | walker.addDep(createDependenceSymbol(moduleNode, container, scope)); 47 | } 48 | 49 | function parseDependencePcall(walker, node, container, scope, parentSymbol) { 50 | let moduleNode = node.arguments[1]; 51 | if (!moduleNode.value) { 52 | return; 53 | } 54 | walker.addDep(createDependenceSymbol(moduleNode, container, scope)); 55 | } 56 | 57 | function createDependenceSymbol(moduleNode, container, scope) { 58 | let moduleName = moduleNode.value; 59 | let m = moduleName.match(/\w+$/); 60 | if (m) { 61 | moduleName = m[0]; 62 | } 63 | 64 | let dep = traits.symbol(moduleName, traits.SymbolKind.dependence, false); 65 | dep.location = utils.getLocation(moduleNode); 66 | dep.scope = scope; 67 | dep.container = container; 68 | 69 | if (moduleNode.value.includes('.')) { 70 | dep.shortPath = moduleNode.value.replace(/\./g, path_1.sep); 71 | } 72 | 73 | return dep; 74 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/CallStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 4 | node.expression && walker.walkNode(node.expression, container, scope, parentSymbol, false); 5 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/Chunk.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | node.body && walker.walkNodes(node.body, container, utils.loc2Range(node.loc), parentSymbol, isDef); 7 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/DoStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | node.body && walker.walkNodes(node.body, container, utils.loc2Range(node.loc), parentSymbol, isDef); 7 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/ElseClause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | walker.walkNodes(node.body, container, utils.loc2Range(node.loc), parentSymbol, isDef); 7 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/ElseifClause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ifClause = require('./IfClause'); 4 | 5 | exports.parse = ifClause.parse; -------------------------------------------------------------------------------- /server/lib/symbol/types/ForGenericStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | var ident = require('./Identifier'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | var newScope = utils.loc2Range(node.loc); 8 | for (var i = 0; i < node.variables.length; i++) { 9 | var variable = node.variables[i]; 10 | ident.parse(walker, variable, container, newScope, node, true); 11 | } 12 | 13 | node.iterators && walker.walkNodes(node.iterators, container, newScope, node, false); 14 | 15 | node.body && walker.walkNodes(node.body, container, newScope, node); 16 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/ForNumericStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | var ident = require('./Identifier'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | var newScope = utils.loc2Range(node.loc); 8 | node.variable && ident.parse(walker, node.variable, container, newScope, node, true); 9 | 10 | ['start', 'end', 'step'].forEach(fid => { 11 | node[fid] && walker.walkNode(node[fid], container, newScope, node, false); 12 | }); 13 | 14 | node.body && walker.walkNodes(node.body, container, newScope, node); 15 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/FunctionDeclaration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const traits = require('../symbol-traits'); 4 | const utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | let id = node.identifier; 8 | //consider anonymouse function 9 | let fname = utils.safeName(id); 10 | let func = undefined; 11 | if (id != null) { 12 | func = traits.symbol(fname, traits.SymbolKind.function, utils.isLocal(id)); // utils.isLocal(node) 13 | func.scope = scope; 14 | func.container = container; 15 | func.location = utils.getLocation(id); 16 | func.bases = utils.extractBases(id); 17 | func.params = []; 18 | 19 | walker.addDef(func); 20 | utils.parseBase(walker, id, container, scope, node, traits); 21 | } 22 | 23 | let newScope = utils.loc2Range(node.loc); 24 | let newContainer = {name: fname}; 25 | if (node.parameters) { 26 | let parameters = node.parameters; 27 | for (let i = 0; i < parameters.length; i++) { 28 | let param = parameters[i]; 29 | let pname = param.name || param.value; 30 | let p = traits.symbol(pname, traits.SymbolKind.parameter, true); 31 | p.container = newContainer; 32 | p.location = utils.loc2Range(param.loc); 33 | p.scope = newScope; 34 | 35 | walker.addDef(p); 36 | 37 | func && func.params.push(pname); 38 | } 39 | } 40 | 41 | walker.walkNodes(node.body, newContainer, newScope, node); 42 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/Identifier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var traits = require('../symbol-traits'); 4 | var utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | var newSymbol = traits.symbol(utils.safeName(node), undefined, utils.isLocal(node)); 8 | newSymbol.container = container; 9 | newSymbol.location = utils.loc2Range(node.loc); 10 | 11 | // if symbol is not local defined, should we defined ? 12 | if (!newSymbol.islocal && isDef === undefined && walker.options.allowDefined) { 13 | let defs = utils.findDefinition(newSymbol, walker.document.definitions); 14 | isDef = !defs || (defs.length == 0); 15 | } 16 | 17 | newSymbol.kind = isDef ? traits.SymbolKind.variable : traits.SymbolKind.reference 18 | isDef && (newSymbol.scope = scope); 19 | 20 | if (isDef) { 21 | walker.addDef(newSymbol); 22 | } else { 23 | walker.addRef(newSymbol); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/lib/symbol/types/IfClause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | var newScope = utils.loc2Range(node.loc); 7 | node.condition && walker.walkNode(node.condition, container, newScope, parentSymbol, false); 8 | walker.walkNodes(node.body, container, newScope, parentSymbol); 9 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/IfStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 4 | node.clauses && walker.walkNodes(node.clauses, container, scope, parentSymbol, false); 5 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/IndexExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 4 | node.index && walker.walkNode(node.index, container, scope, parentSymbol, false); 5 | node.base && walker.walkNode(node.base, container, scope, parentSymbol, false); 6 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/LocalStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const traits = require('../symbol-traits'); 4 | const utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | for (let i = 0; i < node.variables.length; i++) { 8 | let variable = node.variables[i]; 9 | let newSymbol = traits.symbol(utils.safeName(variable), traits.SymbolKind.variable, true); 10 | newSymbol.scope = scope; 11 | newSymbol.container = container; 12 | newSymbol.location = utils.loc2Range(variable.loc); 13 | 14 | walker.addDef(newSymbol); 15 | 16 | if (node.init && node.init[i]) { 17 | let init = node.init[i]; 18 | if (init.type == 'TableConstructorExpression') { 19 | newSymbol.kind = traits.SymbolKind.class; 20 | walker.walkNodes(init.fields, container, scope, newSymbol, true); 21 | } else if (init.type == 'FunctionDeclaration') { 22 | newSymbol.kind = traits.SymbolKind.function; 23 | newSymbol.params = init.parameters.map(p => { return p.name; }); 24 | walker.walkNodes(init, container, scope, newSymbol, true); 25 | } else { 26 | newSymbol.alias = init.name; //这里只处理local x = y的场景,alias=y 27 | walker.walkNode(init, container, scope, parentSymbol, false); 28 | } 29 | //todo: parse pcall, require for module dependence 30 | if (init.type == 'CallExpression' || init.type == 'StringCallExpression') { 31 | switch (init.base.name) { 32 | case 'require': 33 | let moduleNode = init.argument || init.arguments[0]; 34 | newSymbol.alias = utils.parseModuleName(moduleNode.value) || ''; 35 | break; 36 | case 'pcall': 37 | if (init.arguments[0].name == 'require') { 38 | newSymbol.alias = utils.parseModuleName(init.arguments[1].value) || ''; 39 | } 40 | break; 41 | default: 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/LogicalExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var binaryExpr = require('./BinaryExpression'); 4 | 5 | exports.parse = binaryExpr.parse; -------------------------------------------------------------------------------- /server/lib/symbol/types/MemberExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var traits = require('../symbol-traits'); 4 | var utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | let name = utils.safeName(node); 8 | let kind = isDef ? traits.SymbolKind.variable : traits.SymbolKind.reference; 9 | let ref = traits.symbol(name, kind, utils.isLocal(node)); 10 | ref.container = container; 11 | ref.location = utils.getLocation(node); 12 | ref.bases = utils.extractBases(node); 13 | if (isDef) { 14 | ref.scope = scope; 15 | walker.addDef(ref); 16 | } else { 17 | walker.addRef(ref); 18 | } 19 | 20 | utils.parseBase(walker, node, container, scope, parentSymbol, traits); 21 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/RepeatStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | var newScope = utils.loc2Range(node.loc); 7 | node.condition && walker.walkNode(node.condition, container, newScope, parentSymbol, false); 8 | node.body && walker.walkNodes(node.body, container, newScope, parentSymbol, isDef); 9 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/ReturnStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.parse = (walker, node, container, scope, parentSymbol) => { 4 | /** process 'return a table' situation like: 5 | * in 'class.lua' 6 | * ```lua 7 | * local class = {} 8 | * function class:funcA() 9 | * ... 10 | * end 11 | * ... 12 | * 13 | * return class 14 | * ``` 15 | */ 16 | if (node.arguments.length === 0) { 17 | return; 18 | } 19 | 20 | if (node.arguments.length === 1 && container.name === '_G') { 21 | let expt = node.arguments[0]; 22 | if (expt.type === 'Identifier') { 23 | returnIdentifier(expt, walker); 24 | } 25 | 26 | walker.walkNodes(node.arguments, container, scope, parentSymbol, false); 27 | 28 | if (expt.type === 'TableConstructorExpression') { 29 | walker.document.returns = walker.document.definitions; 30 | walker.document.returns.forEach(d => { 31 | d.returnMode = true; 32 | }); 33 | } 34 | } else { 35 | walker.walkNodes(node.arguments, container, scope, parentSymbol, false); 36 | } 37 | } 38 | 39 | function returnIdentifier(expt, walker) { 40 | let definitions = walker.document.definitions; 41 | walker.document.returns = definitions.filter(d => { 42 | return d.bases[0] === expt.name; 43 | }); 44 | 45 | if (walker.document.returns) { 46 | walker.document.returns.forEach(d => { 47 | d.returnMode = true; 48 | }); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /server/lib/symbol/types/StringCallExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var callExpr = require('./CallExpression'); 4 | 5 | exports.parse = callExpr.parse; -------------------------------------------------------------------------------- /server/lib/symbol/types/TableCallExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var traits = require('../symbol-traits'); 4 | var utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 7 | var call = traits.symbol(utils.safeName(node.base), traits.SymbolKind.reference, utils.isLocal(node.base)); 8 | call.container = container; 9 | call.location = utils.getLocation(node.base); 10 | call.bases = utils.extractBases(node.base); 11 | 12 | walker.addRef(call); 13 | utils.parseBase(walker, node.base, container, scope, node, traits); 14 | 15 | node.arguments && walker.walkNodes(node.arguments, container, scope, parentSymbol, false); 16 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/TableConstructorExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 4 | walker.walkNodes(node.fields, container, scope, parentSymbol, isDef); 5 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/TableKeyString.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var traits = require('../symbol-traits'); 4 | var utils = require('../utils'); 5 | 6 | exports.parse = (walker, node, container, scope, parentSymbol) => { 7 | //parse key: 8 | var prop = traits.symbol(utils.safeName(node.key), traits.SymbolKind.property, false); 9 | prop.scope = scope; 10 | prop.container = container; 11 | prop.location = utils.loc2Range(node.key.loc); 12 | if (parentSymbol.name) { 13 | prop.bases = prop.bases.concat(parentSymbol.bases, parentSymbol.name); 14 | } 15 | 16 | walker.addDef(prop); 17 | 18 | if (node.value.type == 'FunctionDeclaration') { 19 | prop.kind = traits.SymbolKind.function; 20 | prop.params = node.value.parameters.map(p => { return p.name; }); 21 | } 22 | 23 | //parse value: 24 | node.value && walker.walkNode(node.value, container, scope, prop, true); 25 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/UnaryExpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 4 | node.argument && walker.walkNode(node.argument, container, scope, parentSymbol, false); 5 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/WhileStatement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../utils'); 4 | 5 | exports.parse = (walker, node, container, scope, parentSymbol, isDef) => { 6 | var newScope = utils.loc2Range(node.loc); 7 | node.condition && walker.walkNode(node.condition, container, newScope, parentSymbol, false); 8 | node.body && walker.walkNodes(node.body, container, newScope, parentSymbol, isDef); 9 | } -------------------------------------------------------------------------------- /server/lib/symbol/types/index.js: -------------------------------------------------------------------------------- 1 | /*jshint nomen:false */ 2 | /*globals require, exports, __dirname */ 3 | 4 | 'use strict'; 5 | 6 | var fs = require('fs'), 7 | loaded = false, 8 | syntaxModules = []; 9 | 10 | exports.get = getSyntax; 11 | 12 | function getSyntax (settings) { 13 | var syntax = {}, name; 14 | 15 | if (loaded === false) { 16 | loadSyntaxModules(); 17 | loaded = true; 18 | } 19 | 20 | for (name in syntaxModules) { 21 | if (syntaxModules.hasOwnProperty(name)) { 22 | setSyntax(syntax, name, settings); 23 | } 24 | } 25 | 26 | return syntax; 27 | } 28 | 29 | function loadSyntaxModules () { 30 | var fileNames, i, fileName, components; 31 | 32 | fileNames = getSyntaxFileNames(); 33 | 34 | for (i = 0; i < fileNames.length; i += 1) { 35 | fileName = fileNames[i]; 36 | components = fileName.split('.'); 37 | 38 | if (isSyntaxDefinition(fileName, components)) { 39 | loadSyntaxModule(components[0]); 40 | } 41 | } 42 | } 43 | 44 | function getSyntaxFileNames () { 45 | return fs.readdirSync(__dirname); 46 | } 47 | 48 | function isSyntaxDefinition (fileName, components) { 49 | if (fs.statSync(pathify(__dirname, fileName)).isFile()) { 50 | return components.length === 2 && components[0] !== 'index' && components[1] === 'js'; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | function pathify (directory, fileName) { 57 | return directory + '/' + fileName; 58 | } 59 | 60 | function loadSyntaxModule (name) { 61 | syntaxModules[name] = require(pathify('.', name)); 62 | } 63 | 64 | function setSyntax (syntax, name, settings) { 65 | syntax[name] = syntaxModules[name].parse; 66 | // console.log(name, syntax[name]); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /server/lib/symbol/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function safeName(node) { 4 | if (node) { 5 | return node.name || safeName(node.identifier) || safeName(node.base); 6 | } else { 7 | return ''; 8 | } 9 | } 10 | 11 | exports.safeName = safeName; 12 | 13 | function extractBases(node) { 14 | var bases = []; 15 | function _helper(_node, _bases) { 16 | if (_node.base && _node.base.type != 'CallExpression') { 17 | _helper(_node.base, _bases); 18 | _bases.push(safeName(_node.base)); 19 | } 20 | } 21 | 22 | _helper(node, bases); 23 | return bases; 24 | } 25 | 26 | exports.extractBases = extractBases; 27 | 28 | function inScope(scope, loc) { 29 | return (scope.start.line <= loc.start.line) && 30 | (scope.end.line >= loc.end.line); 31 | } 32 | 33 | exports.inScope = inScope; 34 | 35 | function findSymbol(symbol, symbols) { 36 | var sameName = symbols.filter(sym => { 37 | return symbol.name == sym.name; 38 | }); 39 | 40 | if (sameName.length == 0) { 41 | return undefined; 42 | } 43 | 44 | var numBases = symbol.bases.length; 45 | var sameBases = sameName.filter(s => { 46 | if (s.bases.length != numBases) { 47 | return false; 48 | } 49 | 50 | for (var i = 0; i < s.bases.length; i++) { 51 | var b = s.bases[i]; 52 | if (b != symbol.bases[i]) { 53 | return false; 54 | } 55 | } 56 | 57 | return true; 58 | }); 59 | 60 | if (sameBases.length == 0) { 61 | return undefined; 62 | } 63 | 64 | var sameScope = sameBases.filter(s => { 65 | return s.scope && inScope(s.scope, symbol.location); 66 | }); 67 | 68 | return sameScope; 69 | } 70 | 71 | exports.findSymbol = findSymbol; 72 | 73 | function isLocal(node) { 74 | if (node) { 75 | return node.isLocal || isLocal(node.base); 76 | } else { 77 | return false; 78 | } 79 | } 80 | 81 | exports.isLocal = isLocal; 82 | 83 | function loc2Range(loc) { 84 | return { 85 | start: {line: loc.start.line - 1, character: loc.start.column}, 86 | end: {line: loc.end.line - 1, character: loc.end.column} 87 | }; 88 | } 89 | 90 | exports.loc2Range = loc2Range; 91 | 92 | function getLocation(node) { 93 | if (node.identifier) { 94 | return getLocation(node.identifier); 95 | } 96 | 97 | return loc2Range(node.loc); //to vscode's Range 98 | } 99 | 100 | exports.getLocation = getLocation; 101 | 102 | function parseBase(walker, node, container, scope, parentSymbol, traits) { 103 | if (node.base === undefined) { 104 | return; 105 | } 106 | 107 | // for func(x):method() 108 | if (node.base.type == 'CallExpression') { 109 | walker.walkNode(node.base, container, scope, parentSymbol, false); 110 | } else { 111 | var name = safeName(node.base); 112 | var newSymbol = traits.symbol(name, traits.SymbolKind.reference, isLocal(node.base)); 113 | newSymbol.container = container; 114 | newSymbol.location = getLocation(node.base); 115 | newSymbol.bases = extractBases(node.base); 116 | 117 | walker.addRef(newSymbol); 118 | } 119 | 120 | parseBase(walker, node.base, container, scope, parentSymbol, traits); 121 | } 122 | 123 | exports.parseBase = parseBase; 124 | 125 | function findDefinition(symbol, defs) { 126 | return defs.forEach(d => { 127 | if (symbol.name !== d.name) { 128 | return false; 129 | } 130 | 131 | if (symbol.bases.length != d.bases.length) { 132 | return false; 133 | } 134 | 135 | if (!inScope(d.scope, symbol.location)) { 136 | return false; 137 | } 138 | 139 | for (let i = 0; i < d.bases.length; i++) { 140 | if (d.bases[i].name !== symbol.bases[i].name) { 141 | return false; 142 | } 143 | } 144 | 145 | return true; 146 | }); 147 | } 148 | 149 | exports.findDefinition = findDefinition; 150 | 151 | function parseModuleName(param) { 152 | if (param) { 153 | return param.match(/\w+$/)[0]; 154 | } 155 | 156 | return undefined; 157 | } 158 | 159 | exports.parseModuleName = parseModuleName; -------------------------------------------------------------------------------- /server/lib/symbol/walker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const utils = require('./utils'); 5 | 6 | class Walker { 7 | constructor(types, options, logger) { 8 | this.types = types; 9 | // Allow defining globals implicitly by setting them. 10 | this.options = { allowDefined: options.allowDefined }; 11 | this.logger = _.isFunction(logger) ? logger: () => {}; 12 | this.document = { 13 | uri: undefined, 14 | module: undefined, 15 | definitions: [], 16 | references: [], 17 | dependences: [], 18 | ast: undefined 19 | }; 20 | } 21 | 22 | walkNode(node, container, scope, parentSymbol, isDef) { 23 | const parse = this.types[node.type]; 24 | if (_.isFunction(parse)) { 25 | parse(this, node, container, scope, parentSymbol, isDef); 26 | } else { 27 | this.logger("[INFO] No parser for type: " + node.type); 28 | } 29 | } 30 | 31 | walkNodes(nodes, container, scope, parentSymbol, isDef) { 32 | if (_.isArray(nodes)) { 33 | nodes.forEach(node => { 34 | this.walkNode(node, container, scope, parentSymbol, isDef); 35 | }); 36 | } else if (_.isObject(nodes)) { 37 | this.walkNode(nodes, container, scope, parentSymbol, isDef); 38 | } 39 | } 40 | 41 | reset() { 42 | this.document = { 43 | uri: undefined, 44 | module: undefined, 45 | definitions: [], 46 | references: [], 47 | dependences: [], 48 | ast: undefined 49 | }; 50 | } 51 | 52 | processDocument(uri, ast) { 53 | this.reset(); 54 | this.document.uri = uri; 55 | this.document.ast = ast; 56 | let matches = uri.match(/(\w+)(\.lua)?$/); 57 | if (matches) { 58 | let fileName = matches[1]; 59 | this.walkNodes(ast.body, { name: '_G' }, utils.loc2Range(ast.loc), { name: fileName, bases: [] }, true); 60 | } 61 | 62 | return this.document; 63 | } 64 | 65 | addMod(mod) { 66 | this.document.module = mod; 67 | } 68 | 69 | addDef(def) { 70 | def.uri = this.document.uri; 71 | this.document.definitions.push(def); 72 | } 73 | 74 | addRef(ref) { 75 | ref.uri = this.document.uri; 76 | this.document.references.push(ref); 77 | } 78 | 79 | addDep(dep) { 80 | this.document.dependences.push(dep); 81 | } 82 | }; 83 | 84 | exports.Walker = Walker; -------------------------------------------------------------------------------- /server/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const engine = require('./lib/engine'); 4 | const uri_1 = require('vscode-uri').default; 5 | const awaiter = require('./providers/lib/awaiter'); 6 | const utils_1 = require('./providers/lib/utils'); 7 | const fs_1 = require('fs'); 8 | const path_1 = require('path'); 9 | const findup = require('find-up'); 10 | 11 | const STD_PRELOADS = { 12 | '5.1': 'stdlibs/5_1.json', 13 | '5.2': 'stdlibs/5_2.json', 14 | '5.3': 'stdlibs/5_3.json' 15 | } 16 | 17 | const LOVE_PRELOAD = 'stdlibs/love.json'; 18 | const LUAJIT_PRELOAD = 'stdlibs/luajit-2_0.json'; 19 | 20 | function loadAll(coder) { 21 | const preloads = coder.settings.preloads; 22 | const luaversion = coder.luaversion; 23 | const filePath = coder.extensionPath + (STD_PRELOADS[luaversion] || 'stdlibs/5_3.json'); 24 | 25 | coder.tracer.info('loading STD library ...'); 26 | engine.loadExtentLib(filePath, "std.lua", coder.tracer); // load stdlib 27 | 28 | preloads.forEach(filePath => { 29 | load(filePath, coder); 30 | }); 31 | 32 | if (coder.settings.useLove) { 33 | coder.tracer.info('loading LOVE library ...'); 34 | engine.loadExtentLib(coder.extensionPath + LOVE_PRELOAD, "love.lua", coder.tracer); 35 | } 36 | 37 | if (coder.settings.useJit) { 38 | coder.tracer.info('loading JIT library ...'); 39 | engine.loadExtentLib(coder.extensionPath + LUAJIT_PRELOAD, "jit.lua", coder.tracer); 40 | } 41 | 42 | // TODO: add watcher for the modification of the rc file to avoid reload vscode. 43 | findup(".luacompleterc", {cwd: coder.workspaceRoot}).then(rcFilePath => { 44 | if (rcFilePath !== undefined && typeof(rcFilePath) === 'string') { 45 | coder.tracer.info('loading luacomplete resource file: ' + rcFilePath); 46 | engine.loadExtentLib(rcFilePath, undefined, coder.tracer); 47 | } 48 | 49 | }); 50 | } 51 | 52 | function load(filePath, coder) { 53 | let stats = fs_1.lstatSync(filePath); 54 | if (!stats) { 55 | coder.tracer.error(`failed to load ${filePath}, not a regular path.`); 56 | return; 57 | } 58 | 59 | if (stats.isFile()) { 60 | coder.tracer.info(`loading ${filePath} ...`); 61 | loadFile(filePath, coder); 62 | return; 63 | } 64 | 65 | if (stats.isDirectory()) { 66 | const moduleWithInitFile = path_1.join(filePath, 'init.lua'); 67 | if (fs_1.existsSync(moduleWithInitFile)) { 68 | coder.tracer.info(`loading ${moduleWithInitFile} ...`); 69 | loadFile(moduleWithInitFile, coder); 70 | } else { 71 | utils_1.searchFile(filePath, coder.settings.search, (root, name) => { 72 | if (path_1.extname(name) === '.lua') { 73 | const fileFound = path_1.resolve(root, name); 74 | coder.tracer.info(`loading ${fileFound} ...`); 75 | loadFile(fileFound, coder); 76 | } 77 | }, () => {}); 78 | } 79 | return; 80 | } 81 | 82 | return; 83 | } 84 | 85 | function loadFile(filePath, coder) { 86 | return awaiter.await(void 0, void 0, void 0, function* () { 87 | const uri = uri_1.file(filePath).toString(); 88 | const document = yield coder.document(uri); 89 | engine.parseDocument(document.getText(), uri, coder.tracer); 90 | }); 91 | } 92 | 93 | exports.loadAll = loadAll; 94 | exports.load = load; 95 | -------------------------------------------------------------------------------- /server/protocols.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var LDocRequest; 4 | (function (LDocRequest) { 5 | LDocRequest.type = { get method() { return "LuaCoderAssist/LDoc"; } } 6 | })(LDocRequest = exports.LDocRequest || (exports.LDocRequest = {})); 7 | 8 | var BustedRequest; 9 | (function (BustedRequest) { 10 | BustedRequest.type = { get method() { return "LuaCoderAssist/Busted"; } } 11 | })(BustedRequest = exports.BustedRequest || (exports.BustedRequest = {})); 12 | -------------------------------------------------------------------------------- /server/providers/completion-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const langserver = require('vscode-languageserver'); 4 | const utils = require('./lib/utils'); 5 | const engine = require('../lib/engine'); 6 | const is = require('../lib/engine/is'); 7 | const awaiter = require('./lib/awaiter'); 8 | const fileManager = require('./lib/file-manager'); 9 | const path = require('path') 10 | 11 | class CompletionProvider { 12 | constructor(coder) { 13 | this.coder = coder; 14 | this.cache = null; 15 | } 16 | 17 | provideCompletions(params) { 18 | return awaiter.await(this, void 0, void 0, function* () { 19 | let uri = params.textDocument.uri; 20 | let position = params.position; 21 | position.character--; 22 | let document = yield this.coder.document(uri); 23 | let ref = utils.symbolAtPosition(position, document, { backward: true }); 24 | if (ref === undefined) { 25 | return undefined; 26 | } 27 | 28 | if (ref.completePath) { 29 | return this._completePath(ref); 30 | } 31 | 32 | if (params.context.triggerCharacter === "\"" || params.context.triggerCharacter === "'") { 33 | return undefined; 34 | } 35 | 36 | let ctx = new engine.CompletionContext(ref.name, ref.range, uri); 37 | ctx.isString = ref.isString; 38 | let items = engine.completionProvider(ctx); 39 | let completionItems = []; 40 | items.forEach((item, index) => { 41 | if (is.luaFunction(item.type)) { 42 | this._completeFunction(item, index, completionItems, !ctx.functionOnly); 43 | return; 44 | } else { 45 | let symbol = langserver.CompletionItem.create(item.name); 46 | symbol.kind = utils.mapToCompletionKind(item.kind); 47 | symbol.data = { index: index }; 48 | completionItems.push(symbol); 49 | } 50 | }); 51 | 52 | this.cache = items; 53 | 54 | return completionItems.length === 0 ? null : completionItems; 55 | }); 56 | } 57 | 58 | resolveCompletion(item) { 59 | if (!item.data) { 60 | return item; 61 | } 62 | 63 | let data = this.cache[item.data.index]; 64 | const override = item.data.override; 65 | const selfAsParam = item.data.selfAsParam; 66 | item.detail = utils.symbolSignature(data, override); 67 | if (data.type) { 68 | const description = data.type.description || ''; 69 | const link = data.type.link; 70 | const desc = (override !== undefined) ? data.type.variants[override].description : description; 71 | item.documentation = { 72 | kind: langserver.MarkupKind.Markdown, 73 | value: desc + (link ? ` \r\n[more...](${link})` : '') 74 | }; 75 | } 76 | const insertParams = this.coder.settings.completion.autoInsertParameters; 77 | utils.functionSnippet(item, data, override, selfAsParam, insertParams); 78 | return item; 79 | } 80 | 81 | _completeFunction(item, index, list, selfAsParam) { 82 | if (item.type.variants) { 83 | const type = item.type; 84 | type.variants.forEach((variant, override) => { 85 | let symbol = langserver.CompletionItem.create(item.name); 86 | symbol.kind = utils.mapToCompletionKind(item.kind); 87 | symbol.data = { index, override, selfAsParam }; 88 | list.push(symbol); 89 | }); 90 | } else { 91 | let symbol = langserver.CompletionItem.create(item.name); 92 | symbol.kind = utils.mapToCompletionKind(item.kind); 93 | symbol.data = { index, selfAsParam }; 94 | list.push(symbol); 95 | } 96 | } 97 | 98 | _completePath(ref) { 99 | const fmInstance = fileManager.instance(); 100 | let refPath = ref.name.replace(/[\.|\/]/g, path.sep); 101 | let matchedFiles = fmInstance.matchPath(refPath); 102 | let pathList = []; 103 | if (path.sep == '\\') { 104 | refPath = refPath.replace(/\\/g, '\\\\'); 105 | } 106 | const subDirNameReg = RegExp(refPath + `([^\./\\\\]+)`); 107 | let existSubDir = {}; 108 | matchedFiles.forEach(file => { 109 | let label; 110 | let detail; 111 | if (refPath === "") { //trigger by ' or " 112 | label = path.basename(file, '.lua'); 113 | detail = file; 114 | } else { 115 | label = subDirName(file, subDirNameReg); 116 | if (!label) { 117 | return; 118 | } 119 | if (existSubDir[label]) { 120 | return; 121 | } else { 122 | existSubDir[label] = true; 123 | detail = ref.name + label; 124 | } 125 | } 126 | 127 | let item = langserver.CompletionItem.create(label); 128 | item.kind = langserver.CompletionItemKind.Module; 129 | item.detail = detail; 130 | pathList.push(item); 131 | }); 132 | return pathList; 133 | } 134 | }; 135 | 136 | function subDirName(filePath, regExp) { 137 | let matches = filePath.match(regExp); 138 | return matches && matches[1]; 139 | } 140 | 141 | exports.CompletionProvider = CompletionProvider; 142 | 143 | -------------------------------------------------------------------------------- /server/providers/definition-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { DefinitionContext, definitionProvider } = require('../lib/engine/definition'); 4 | const awaiter = require('./lib/awaiter'); 5 | const utils_1 = require('./lib/utils'); 6 | const langserver_1 = require('vscode-languageserver'); 7 | 8 | class DefinitionProvider { 9 | constructor(coder) { 10 | this.coder = coder; 11 | } 12 | 13 | provideDefinitions(params) { 14 | return awaiter.await(this, void 0, void 0, function* () { 15 | let uri = params.textDocument.uri; 16 | let position = params.position; 17 | let document = yield this.coder.document(uri); 18 | let ref = utils_1.symbolAtPosition(position, document, { backward: true, forward: true }); 19 | if (ref === undefined) { 20 | return []; 21 | } 22 | 23 | let symbols = definitionProvider(new DefinitionContext(ref.name, ref.range, uri)) 24 | .filter(symbol => { 25 | return symbol.uri !== null; 26 | }); 27 | 28 | const defs = []; 29 | for (let i = 0; i < symbols.length; ++i) { 30 | const symbol = symbols[i]; 31 | const document = yield this.coder.document(symbol.uri); 32 | const start = document.positionAt(symbol.location[0]); 33 | const end = document.positionAt(symbol.location[1]); 34 | defs.push({ 35 | uri: symbol.uri, 36 | name: symbol.name, 37 | range: langserver_1.Range.create(start, end) 38 | }); 39 | } 40 | return defs; 41 | }); 42 | } 43 | }; 44 | 45 | exports.DefinitionProvider = DefinitionProvider; -------------------------------------------------------------------------------- /server/providers/diagnostic-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const execFile = require('child_process').execFile; 4 | const linters_1 = require('./lib/linters'); 5 | const awaiter = require('./lib/awaiter'); 6 | 7 | const MINIMUN_TIMEOUT = 500; // ms 8 | 9 | class DiagnosticProvider { 10 | constructor(coder) { 11 | this.coder = coder; 12 | this.lastTasks = {}; 13 | this._initLinters(); 14 | } 15 | 16 | _initLinters() { 17 | this.linters = []; 18 | this.linters.push(new linters_1.Luacheck(this.coder)); 19 | } 20 | 21 | provideDiagnostics(uri) { 22 | this._updateTask(uri); 23 | } 24 | 25 | _updateTask(uri) { 26 | let lastTask = this._ensureTask(uri); 27 | let timerId = lastTask.timerId; 28 | if (timerId) { 29 | /*定时器还没超时,则取消,重新起定时器*/ 30 | clearTimeout(timerId); 31 | } 32 | lastTask.timerId = this._newTimeoutTask(uri, lastTask.timeout); 33 | } 34 | 35 | _ensureTask(uri) { 36 | let lastTask = this.lastTasks[uri]; 37 | if (!lastTask) { 38 | lastTask = { 39 | version: 0, 40 | timeout: MINIMUN_TIMEOUT, 41 | timerId: undefined, 42 | }; 43 | this.lastTasks[uri] = lastTask; 44 | } 45 | return lastTask; 46 | } 47 | 48 | _newTimeoutTask(uri, timeout) { 49 | return setTimeout((uri) => { 50 | this._lintTask(uri); 51 | }, timeout, uri); 52 | } 53 | 54 | _lintTask(uri) { 55 | return awaiter.await(this, void 0, void 0, function* () { 56 | const start = process.hrtime(); 57 | let lastTask = this.lastTasks[uri]; 58 | lastTask.timerId = undefined; 59 | 60 | const document = yield this.coder.document(uri); 61 | const version = document.version; 62 | if (version === lastTask.version) { 63 | /*没有新的修改*/ 64 | return; 65 | } 66 | lastTask.version = version; 67 | const text = document.getText(); 68 | const promises = []; 69 | let timeout = 0; 70 | this.linters.forEach(linter => { 71 | if (linter.precondiction && !linter.precondiction(document)) { 72 | return; 73 | } 74 | 75 | const command = linter.command(document); 76 | let promise = this._run(command, text).then(() => { 77 | this.coder.sendDiagnostics(uri, []); 78 | timeout = Math.max(timeout, this._elapsedTime(start)); 79 | }, nok => { 80 | const diagnostics = linter.parseDiagnostics(nok); 81 | this.coder.sendDiagnostics(uri, diagnostics); 82 | timeout = Math.max(timeout, this._elapsedTime(start)); 83 | }); 84 | 85 | promises.push(promise); 86 | 87 | }, this); 88 | 89 | Promise.all(promises).then(() => { 90 | if (timeout > MINIMUN_TIMEOUT) { 91 | lastTask.timeout = timeout; 92 | } 93 | this.coder.tracer.info(`luacheck check ${uri} in ${timeout} ms.`); 94 | }); 95 | }); 96 | } 97 | 98 | _run(linter, input) { 99 | return new Promise((resolve, reject) => { 100 | /*重新创建一个检查进程*/ 101 | try { 102 | let proc = execFile(linter.cmd, linter.args, { cwd: linter.cwd }, (error, stdout, stderr) => { 103 | if (error != null) { 104 | reject({ error: error, stdout: stdout, stderr: stderr }); 105 | } else { 106 | resolve({ error: error, stdout: stdout, stderr: stderr }); 107 | } 108 | }); 109 | 110 | proc.stdin.end(input); 111 | } catch (e) { 112 | console.error(`execute '${linter.cmd} ${linter.args.join(' ')}' failed.`); 113 | console.error(e); 114 | reject({error: e}); 115 | } 116 | }); 117 | } 118 | 119 | _elapsedTime(start) { 120 | const duration = process.hrtime(start); 121 | const timeInMs = (duration[0] * 1000) + Math.floor(duration[1] / 1000000); 122 | return timeInMs; 123 | } 124 | } 125 | 126 | exports.DiagnosticProvider = DiagnosticProvider; -------------------------------------------------------------------------------- /server/providers/format-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fmt = require('lua-fmt'); 3 | const langserver = require('vscode-languageserver'); 4 | const awaiter = require('./lib/awaiter'); 5 | 6 | class FormatProvider { 7 | constructor(coder) { 8 | this.coder = coder; 9 | } 10 | 11 | formatOnTyping(params) { 12 | let uri = params.textDocument.uri; 13 | let opt = this.coder.settings.format; 14 | 15 | let pos = params.position; 16 | let char = params.ch; 17 | 18 | // this.coder.tracer.info(JSON.stringify(params)); 19 | 20 | return []; 21 | } 22 | 23 | formatRangeText(params) { 24 | return awaiter.await(this, void 0, void 0, function* () { 25 | let uri = params.textDocument.uri; 26 | let opt = this.coder.settings.format; 27 | 28 | let document = yield this.coder.document(uri); 29 | 30 | let text 31 | let range = params.range; 32 | 33 | text = document.getText(); 34 | if (!range) { 35 | let endPos = document.positionAt(text.length); 36 | range = langserver.Range.create(0, 0, endPos.line, endPos.character); 37 | } else { 38 | text = text.substring(document.offsetAt(range.start), document.offsetAt(range.end)); 39 | } 40 | 41 | try { 42 | let formattedText = fmt.formatText(text, this._formatOptions(opt)); 43 | return [langserver.TextEdit.replace(range, formattedText)]; 44 | } catch (err) { 45 | return []; 46 | } 47 | }); 48 | } 49 | 50 | _formatOptions(userOptions) { 51 | return { 52 | lineWidth: userOptions.lineWidth || 120, 53 | indentCount: userOptions.indentCount || 4, 54 | quotemark: userOptions.quotemark || 'single' 55 | }; 56 | } 57 | }; 58 | 59 | exports.FormatProvider = FormatProvider; 60 | -------------------------------------------------------------------------------- /server/providers/hover-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { DefinitionContext, definitionProvider, LoadedPackages } = require('../lib/engine'); 4 | const utils = require('./lib/utils'); 5 | const awaiter = require('./lib/awaiter'); 6 | const langserver_1 = require('vscode-languageserver'); 7 | 8 | class HoverProvider { 9 | constructor(coder) { 10 | this.coder = coder; 11 | } 12 | 13 | provideHover(params) { 14 | return awaiter.await(this, void 0, void 0, function* () { 15 | let position = params.position; 16 | if (position.character == 0) { 17 | return undefined; 18 | } 19 | 20 | let uri = params.textDocument.uri; 21 | let document = yield this.coder.document(uri); 22 | let ref = utils.symbolAtPosition(position, document, { backward: true, forward: true }); 23 | if (ref === undefined || ref.name === "") { 24 | return undefined; 25 | } 26 | 27 | let defs = definitionProvider(new DefinitionContext(ref.name, ref.range, uri)); 28 | 29 | let hovers = []; 30 | defs.forEach(d => { 31 | const variants = d.type.variants; 32 | if (!variants) { 33 | let hover = this._makeHover(d); 34 | hovers.push(hover); 35 | } else { 36 | variants.forEach((_, override) => { 37 | let hover = this._makeHover(d, override); 38 | hovers.push(hover); 39 | }); 40 | } 41 | }); 42 | 43 | return { 44 | contents: { 45 | kind: langserver_1.MarkupKind.Markdown, 46 | value: hovers.join(' \r\n') 47 | } 48 | }; 49 | }); 50 | } 51 | 52 | _makeHover(symbol, override) { 53 | const desc = (override !== undefined) ? symbol.type.variants[override].description : (symbol.type.description || ''); 54 | const link = symbol.type.link; 55 | const more = (link ? ` \r\n[more...](${link})` : ''); 56 | return `\`\`\`lua\n${utils.symbolSignature(symbol, override)}\r\n\`\`\`` + `\r\n${desc}${more}`; 57 | } 58 | }; 59 | 60 | exports.HoverProvider = HoverProvider; -------------------------------------------------------------------------------- /server/providers/ldoc-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const engine = require('../lib/engine'); 4 | const message_1 = require('./lib/message'); 5 | const utils_1 = require('./lib/utils'); 6 | const awaiter = require('./lib/awaiter'); 7 | 8 | class LDocProvider { 9 | constructor(coder) { 10 | this.coder = coder; 11 | } 12 | 13 | onRequest(params) { 14 | return this.provideFunctionDoc(params); 15 | } 16 | 17 | provideFunctionDoc(params) { 18 | return awaiter.await(this, void 0, void 0, function* () { 19 | let position = params.position; 20 | let uri = params.uri; 21 | 22 | let doc = yield this.coder.document(uri); 23 | if (!doc) { 24 | return message_1.error('null document.', -1); 25 | } 26 | 27 | let ref = utils_1.symbolAtPosition(position, doc, { backward: true, forward: true }); 28 | if (ref === undefined) { 29 | return message_1.info('no symbol found at the cursor psotion.', 0); 30 | } 31 | 32 | let funcs = engine.definitionProvider(new engine.DefinitionContext(ref.name, ref.range, uri)) 33 | .filter(symbol => { 34 | return engine.is.luaFunction(symbol.type) 35 | && symbol.location[0] == ref.range[0] 36 | && symbol.location[1] == ref.range[1] 37 | }); 38 | let def = funcs[0]; 39 | if (!def) { 40 | return message_1.info('not function definition.', 0); 41 | } 42 | 43 | let docString = 44 | '--- ${1:function summary description.}\n' + 45 | '-- ${2:function detail description.}\n'; 46 | 47 | let ftype = def.type; 48 | let tabIndex = 3; 49 | ftype.args.forEach(param => { 50 | docString += `-- @tparam \${${tabIndex++}:type} ${param.name} \${${tabIndex++}:description}\n`; 51 | }); 52 | 53 | ftype.returns.forEach((ret) => { 54 | const retType = engine.typeOf(ret); 55 | docString += `-- @treturn ${retType.typeName} \${${tabIndex++}:description.}\n`; 56 | }); 57 | 58 | let settings = this.coder.settings.ldoc; 59 | if (settings.authorInFunctionLevel) { 60 | docString += `-- @author ${settings.authorName}\n`; 61 | } 62 | 63 | return { 64 | uri: uri, 65 | location: { line: position.line, character: 0 }, 66 | doc: docString 67 | }; 68 | }); 69 | } 70 | }; 71 | 72 | exports.LDocProvider = LDocProvider; 73 | -------------------------------------------------------------------------------- /server/providers/lib/awaiter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @returns {Promise} 5 | */ 6 | let __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 7 | return new (P || (P = Promise))(function (resolve, reject) { 8 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 9 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 10 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 11 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 12 | }); 13 | }; 14 | 15 | exports.await = __awaiter; 16 | -------------------------------------------------------------------------------- /server/providers/lib/document-symbol.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('../../lib/symbol/utils'); 4 | 5 | class DocumentSymbol { 6 | constructor(uri) { 7 | this.document = { 8 | uri: uri, 9 | module: undefined, 10 | definitions: undefined, 11 | references: undefined, 12 | dependences: undefined, 13 | ast: undefined 14 | }; 15 | } 16 | 17 | moduleName() { 18 | if (this.document.module) { 19 | return this.document.module.name; 20 | } 21 | 22 | return undefined; 23 | } 24 | 25 | isModule() { 26 | return !!this.document.module; 27 | } 28 | 29 | isReturnMode() { 30 | return !!this.document.returns; 31 | } 32 | 33 | definitions() { 34 | return this.document.definitions || []; 35 | } 36 | 37 | returns() { 38 | return this.document.returns || []; 39 | } 40 | 41 | references() { 42 | return this.document.references || []; 43 | } 44 | 45 | dependences() { 46 | return this.document.dependences || []; 47 | } 48 | 49 | findDefinitions(symbol) { 50 | return utils.findSymbol(symbol, this.definitions()); 51 | } 52 | 53 | findReferences(symbol) { 54 | return utils.findSymbol(symbol, this.references()); 55 | } 56 | }; 57 | 58 | exports.DocumentSymbol = DocumentSymbol; -------------------------------------------------------------------------------- /server/providers/lib/file-manager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const utils_1 = require('./utils'); 4 | const tracer = require('../../tracer'); 5 | const path_1 = require('path'); 6 | const fs = require('fs'); 7 | 8 | const MODULE_NAME_REGEX = /(\w+)?[\\\\|/]?(\w+)\.lua/; 9 | class FileManager { 10 | constructor() { 11 | this._moduleFileMap = {}; 12 | this._files = []; 13 | this._roots = []; 14 | this._luaPaths = []; 15 | } 16 | 17 | reset() { 18 | this._moduleFileMap = {}; 19 | this._roots = []; 20 | } 21 | 22 | getFiles(moduleName) { 23 | if (this._luaPaths.length > 0) { 24 | /* 用来解决多个不同目录下的同名文件导致的符号解析不正确的问题, 25 | * 如果按路径找不到,则退化到默认版本 26 | */ 27 | for (let i = 0; i < this._luaPaths.length; i++) { 28 | const searchPath = this._luaPaths[i].replace('?', moduleName); 29 | if (fs.existsSync(searchPath)) { 30 | return [searchPath]; 31 | } 32 | } 33 | } 34 | return this._moduleFileMap[moduleName] || []; 35 | } 36 | 37 | setRoots(rootPaths) { 38 | this._roots = rootPaths; 39 | } 40 | 41 | /** 42 | * @param {String} luaPath 43 | */ 44 | addLuaPath(luaPath) { 45 | this._luaPaths.push(...luaPath.split(';')); 46 | } 47 | 48 | addFile(filePath) { 49 | const matches = MODULE_NAME_REGEX.exec(filePath); 50 | if (!matches) { 51 | let baseName = path_1.basename(filePath, ".lua"); 52 | this.addModule(baseName, filePath); 53 | } else { 54 | let dirName = matches[1]; 55 | let baseName = matches[2]; 56 | if (baseName === 'init' && dirName) { 57 | // 优先插入以dirName为moduleName的记录 58 | this.addModule(dirName, filePath); 59 | } 60 | this.addModule(baseName, filePath); 61 | } 62 | } 63 | 64 | addModule(moduleName, file) { 65 | this._moduleFileMap[moduleName] = this._moduleFileMap[moduleName] || []; 66 | this._moduleFileMap[moduleName].push(file); 67 | this._files.push(file); 68 | } 69 | 70 | delFile(filePath) { 71 | const matches = MODULE_NAME_REGEX.exec(filePath); 72 | const moduleNames = []; 73 | if (!matches) { 74 | moduleNames.push(path_1.basename(filePath, '.lua')); 75 | } else { 76 | matches[1] && moduleNames.push(matches[1]); 77 | matches[2] && moduleNames.push(matches[2]); 78 | } 79 | moduleNames.forEach(name => { 80 | this.delModule(name, filePath); 81 | }); 82 | } 83 | 84 | delModule(moduleName, filePath) { 85 | let files = this._moduleFileMap[moduleName]; 86 | if (!files) { 87 | return; 88 | } 89 | 90 | let index = files.indexOf(filePath); 91 | index >= 0 && files.splice(index, 1); 92 | index = this._files.indexOf(filePath); 93 | index >= 0 && this._files.splice(index, 1); 94 | } 95 | 96 | searchFiles(options, extname) { 97 | let trace = tracer.instance(); 98 | for (let i = 0; i < this._roots.length; i++) { 99 | let root_ = this._roots[i]; 100 | trace.info(`search ${root_} begin.`) 101 | utils_1.searchFile(root_, options, (root, name) => { 102 | if (path_1.extname(name) == extname) { 103 | this.addFile(path_1.resolve(root, name)); 104 | } 105 | }, (path_) => { 106 | trace.info(`search ${path_} end.`) 107 | }); 108 | } 109 | } 110 | 111 | matchPath(pathSegment) { 112 | return this._files.filter(file => { 113 | return file.includes(pathSegment); 114 | }); 115 | } 116 | }; 117 | 118 | var _instance = undefined; 119 | /** 120 | * @returns {FileManager} 121 | */ 122 | exports.instance = () => { 123 | if (_instance === undefined) { 124 | _instance = new FileManager() 125 | } 126 | 127 | return _instance; 128 | }; -------------------------------------------------------------------------------- /server/providers/lib/linters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const engine = require('../../lib/engine'); 4 | const utils_1 = require('./utils'); 5 | const uri_1 = require('vscode-uri').default; 6 | const langserver_1 = require('vscode-languageserver'); 7 | const path_1 = require('path'); 8 | const fs_1 = require('fs'); 9 | const os = require('os') 10 | 11 | // default to 64-bit windows luacheck.exe, from https://github.com/mpeterv/luacheck/releases 12 | const default_luacheck_executor = path_1.resolve(__dirname, '../../../3rd/luacheck/luacheck.exe'); 13 | const luacheck_regex = /^(.+):(\d+):(\d+)-(\d+): \(([EW])(\d+)\) (.+)$/; 14 | const defaultOpt = ['--no-color', '--codes', '--ranges', '--formatter', 'plain']; 15 | 16 | function isFileSync(aPath) { 17 | try { 18 | return fs_1.statSync(aPath).isFile(); 19 | } catch (e) { 20 | if (e.code === 'ENOENT') { 21 | return false; 22 | } else { 23 | throw e; 24 | } 25 | } 26 | } 27 | 28 | function getLuacheckExecutor() { 29 | let osType = os.type().toLowerCase(); 30 | if (osType.includes('windows')) { 31 | return default_luacheck_executor; 32 | } else { 33 | return utils_1.getExePath('luacheck'); 34 | } 35 | } 36 | 37 | class Luacheck { 38 | constructor(coder) { 39 | this.coder = coder; 40 | this.exePath = getLuacheckExecutor(); 41 | console.info(`[INFO] using '${this.exePath}'`) 42 | } 43 | 44 | command(document) { 45 | const settings = this.coder.settings.luacheck; 46 | let args = []; 47 | let luacheckrc_path 48 | 49 | if (settings.automaticOption) { 50 | this.automaticOptions(settings, args, document); 51 | } else { 52 | const luacheckrc = path_1.resolve(settings.configFilePath, ".luacheckrc"); 53 | if (isFileSync(luacheckrc)) { 54 | args.push('--config', luacheckrc); 55 | luacheckrc_path = path_1.dirname(luacheckrc) 56 | } 57 | } 58 | 59 | args.push(...defaultOpt); 60 | const fileName = uri_1.parse(document.uri).fsPath; 61 | args.push("--filename", fileName, "-"); //use stdin 62 | 63 | let cmd = settings.execPath || this.exePath; 64 | let cwd = path_1.dirname(luacheckrc_path || fileName); 65 | 66 | return { 67 | cmd: cmd, 68 | cwd: cwd, 69 | args: args 70 | }; 71 | } 72 | 73 | automaticOptions(settings, args, document) { 74 | let std = settings.std; 75 | let luaversion = this.coder.settings.luaparse.luaversion; 76 | if (luaversion === 5.1) { 77 | std.push('lua51'); 78 | } 79 | else if (luaversion === 5.2) { 80 | std.push('lua52'); 81 | } 82 | else if (luaversion === 5.3) { 83 | std.push('lua53'); 84 | } 85 | if (std.length > 0) { 86 | args.push('--std', std.join('+')); 87 | } 88 | if (settings.ignore.length > 0) { 89 | args.push("-i"); 90 | args.push(...settings.ignore); 91 | } 92 | if (this.coder.settings.format.lineWidth > 0) { 93 | args.push('--max-line-length', this.coder.settings.format.lineWidth); 94 | } 95 | 96 | args.push(...settings.options); 97 | const jobs = settings.jobs; 98 | if (jobs > 1) { 99 | args.push('-j', jobs); 100 | } 101 | let mdl = engine.LoadedPackages[document.uri]; 102 | let globals = settings.globals; 103 | if (mdl) { 104 | mdl.type.imports.forEach(im => { 105 | globals.push(im.name); 106 | }); 107 | } 108 | if (globals.length > 0) { 109 | args.push('--read-globals'); 110 | args.push(...globals); 111 | } 112 | } 113 | 114 | parseDiagnostics(data) { 115 | let diagnostics = []; 116 | 117 | if (data.error != null && data.error.message === 'stdout maxBuffer exceeded.') { 118 | return diagnostics; 119 | } 120 | 121 | if (data.stderr != null && data.stderr.length > 0) { 122 | diagnostics.push(langserver_1.Diagnostic.create( 123 | langserver_1.Range.create(0, 0, 0, 0), 124 | data.stderr, 125 | langserver_1.DiagnosticSeverity.Error, 126 | -1, "luacheck" 127 | )); 128 | return diagnostics; 129 | } 130 | 131 | if (data.stdout === undefined) { 132 | return diagnostics; 133 | } 134 | 135 | let maxProblems = this.coder.settings.luacheck.maxProblems; 136 | 137 | //luacheck output to stdout channal 138 | const lines = data.stdout.split(/\r\n|\r|\n/); 139 | for (let i = 0; i < lines.length; i++) { 140 | let line = lines[i]; 141 | if (diagnostics.length > maxProblems) { 142 | break; 143 | } 144 | 145 | let matched = luacheck_regex.exec(line); 146 | if (!matched) { 147 | continue; 148 | } 149 | 150 | let lineNo = parseInt(matched[2]); 151 | let schar = parseInt(matched[3]); 152 | let echar = parseInt(matched[4]); 153 | let eType = this._toDiagnosticSeverity(matched[5]); 154 | let eCode = parseInt(matched[6]) 155 | let errMsg = matched[7]; 156 | 157 | diagnostics.push(langserver_1.Diagnostic.create( 158 | langserver_1.Range.create(lineNo - 1, schar - 1, lineNo - 1, echar), 159 | errMsg, eType, eCode, "luacheck" 160 | )); 161 | } 162 | 163 | return diagnostics; 164 | } 165 | 166 | precondiction(document) { 167 | const settings = this.coder.settings.luacheck; 168 | if (!settings.enable) { 169 | this.coder.sendDiagnostics(document.uri, []); 170 | return false; 171 | } 172 | return this._sizeCheck(document); 173 | } 174 | 175 | _sizeCheck(document) { 176 | const text = document.getText(); 177 | const maxSize = this.coder.settings.luacheck.fileSizeLimit * 1024; 178 | if (text.length > maxSize) { 179 | this.coder.sendDiagnostics(document.uri, [this._lengthOverWarning(document.positionAt(maxSize))]); 180 | return false; 181 | } 182 | 183 | return true; 184 | } 185 | 186 | _toDiagnosticSeverity(errCode) { 187 | switch (errCode) { 188 | case "E": return langserver_1.DiagnosticSeverity.Error; 189 | case "W": return langserver_1.DiagnosticSeverity.Warning; 190 | default: return langserver_1.DiagnosticSeverity.Information; 191 | } 192 | } 193 | 194 | _lengthOverWarning(position) { 195 | return langserver_1.Diagnostic.create( 196 | langserver_1.Range.create(position, position), 197 | 'File size is over the config file size limit.', 198 | langserver_1.DiagnosticSeverity.Hint, undefined, 199 | 'luacheck'); 200 | } 201 | }; 202 | 203 | exports.Luacheck = Luacheck; 204 | -------------------------------------------------------------------------------- /server/providers/lib/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function error(message, code) { 4 | return { 5 | type: 'error', 6 | code: code, 7 | message: message 8 | }; 9 | } 10 | 11 | exports.error = error; 12 | 13 | function warn(message, code) { 14 | return { 15 | type: 'warn', 16 | code: code, 17 | message: message 18 | }; 19 | } 20 | 21 | exports.warn = warn; 22 | 23 | function info(message, code) { 24 | return { 25 | type: 'info', 26 | code: code, 27 | message: message 28 | }; 29 | } 30 | 31 | exports.info = info; 32 | -------------------------------------------------------------------------------- /server/providers/lib/symbol-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var walker_1 = require('../../lib/symbol/walker'); 4 | var types_1 = require('../../lib/symbol/types'); 5 | var docsymbol_1 = require('./document-symbol'); 6 | var luaparse_1 = require('luaparse'); 7 | 8 | const defaultOptions = { 9 | locations: true, 10 | scope: true, 11 | comments: false, 12 | luaversion: 5.1, 13 | allowDefined: true 14 | } 15 | 16 | class SymbolParser { 17 | constructor(coder) { 18 | this.coder = coder; 19 | this.options = defaultOptions; 20 | } 21 | 22 | updateOptions(settings) { 23 | this.options.allowDefined = settings.allowDefined; 24 | this.options.luaversion = settings.luaversion; 25 | } 26 | 27 | parse(uri, content, maxLine) { 28 | if (!content) { 29 | return Promise.reject({message: 'null content'}); 30 | } 31 | 32 | return new Promise((resolve, reject) => { 33 | let docSymbol = new docsymbol_1.DocumentSymbol(uri); 34 | let ast = luaparse_1.parse(content.toString('utf8'), this.options); 35 | let walker = new walker_1.Walker(types_1.get({}), this.options); 36 | 37 | // fix for completion at end of file 38 | ast.loc.end.line = maxLine + 1; 39 | try { 40 | docSymbol.document = walker.processDocument(uri, ast); 41 | } catch (error) { 42 | reject(error); 43 | } 44 | 45 | resolve(docSymbol); 46 | }); 47 | } 48 | } 49 | 50 | exports.SymbolParser = SymbolParser; -------------------------------------------------------------------------------- /server/providers/rename-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils_1 = require('./lib/utils'); 4 | const awaiter = require('./lib/awaiter'); 5 | const langserver = require('vscode-languageserver'); 6 | 7 | class RenameProvider { 8 | constructor(coder) { 9 | this.coder = coder; 10 | } 11 | 12 | provideRename(params) { 13 | return awaiter.await(this, void 0, void 0, function* () { 14 | let newName = params.newName; 15 | if (!(/^\w+$/.test(newName))) { 16 | return { 17 | code: -9, 18 | message: 'invalid newName "' + newName + '"' 19 | } 20 | } 21 | 22 | let uri = params.textDocument.uri; 23 | let document = yield this.coder.document(uri); 24 | let ref = utils_1.symbolAtPosition(params.position, document, { backward: true, forward: true }); 25 | if (ref === undefined) { 26 | return { 27 | code: -8, 28 | message: 'invalid expression' 29 | } 30 | } 31 | 32 | // let sm = symbol_manager.symbolManager(); 33 | // let docsym = sm.documentSymbol(uri); 34 | let docsym = undefined; 35 | if (!docsym) { 36 | return { 37 | code: -7, 38 | message: 'document parse failed' 39 | }; 40 | } 41 | 42 | let defs = utils_1.filterModDefinitions(docsym.definitions(), ref, utils_1.preciseCompareName); 43 | if (defs.length === 0) { 44 | return { 45 | code: -6, 46 | message: 'rename can only apply to local defined variables' 47 | }; 48 | } 49 | 50 | let refs = utils_1.findAllReferences(docsym.references(), defs[0]); 51 | 52 | let textEdits = defs.concat(refs).map(o => { 53 | return langserver.TextEdit.replace(o.location, newName); 54 | }); 55 | 56 | return { 57 | changes: { 58 | [uri]: textEdits 59 | } 60 | }; 61 | }); 62 | } 63 | }; 64 | 65 | exports.RenameProvider = RenameProvider; 66 | -------------------------------------------------------------------------------- /server/providers/signature-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('./lib/utils'); 4 | const awaiter = require('./lib/awaiter'); 5 | const engine = require('../lib/engine'); 6 | const is = require('../lib/engine/is'); 7 | const langsever = require('vscode-languageserver'); 8 | 9 | class SignatureProvider { 10 | constructor(coder) { 11 | this.coder = coder; 12 | } 13 | 14 | provideSignatureHelp(params) { 15 | return awaiter.await(this, void 0, void 0, function* () { 16 | let uri = params.textDocument.uri; 17 | let pos = params.position; 18 | let doc = yield this.coder.document(uri); 19 | let ref = utils.signatureContext(doc.getText(), doc.offsetAt(pos)); 20 | if (ref === undefined) { 21 | return undefined; 22 | } 23 | 24 | let defs = engine.definitionProvider(new engine.DefinitionContext(ref.name, ref.range, uri)); 25 | let signatures = []; 26 | defs.forEach(d => { 27 | if (!is.luaFunction(d.type)) { 28 | return; 29 | } 30 | 31 | if (!d.type.variants) { 32 | let item = this._newSignature(d, d.type.args, d.type.description); 33 | signatures.push(item); 34 | } else { 35 | d.type.variants.forEach((variant, idx) => { 36 | let desc = variant.description || d.type.description; 37 | let item = this._newSignature(d, variant.args, desc, idx); 38 | signatures.push(item); 39 | }); 40 | } 41 | 42 | }); 43 | 44 | return { 45 | signatures: signatures, 46 | activeSignature: signatures.length > 0 ? 0 : null, 47 | activeParameter: signatures.length > 0 ? ref.param_id : null 48 | }; 49 | }); 50 | } 51 | 52 | _newSignature(d, args, doc, idx) { 53 | let item = langsever.SignatureInformation.create(utils.symbolSignature(d, idx)); 54 | item.documentation = this._newDocumentation(doc); 55 | args.forEach(p => { 56 | item.parameters.push(langsever.ParameterInformation.create(p.name)); 57 | }); 58 | 59 | return item; 60 | } 61 | 62 | _newDocumentation(doc) { 63 | return doc && { 64 | kind: langsever.MarkupKind.Markdown, 65 | value: doc 66 | }; 67 | } 68 | }; 69 | 70 | exports.SignatureProvider = SignatureProvider; -------------------------------------------------------------------------------- /server/providers/symbol-provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { LoadedPackages } = require('../lib/engine/luaenv'); 4 | const { LuaSymbolKind, LuaSymbol } = require('../lib/engine/symbol'); 5 | const { typeOf } = require('../lib/engine/typeof'); 6 | const is = require('../lib/engine/is'); 7 | const utils_1 = require('../lib/engine/utils'); 8 | const utils_2 = require('./lib/utils'); 9 | const awaiter = require('./lib/awaiter'); 10 | const langserver_1 = require('vscode-languageserver'); 11 | 12 | function mapSymbolKind(kind) { 13 | switch (kind) { 14 | case LuaSymbolKind.function: return langserver_1.SymbolKind.Function; 15 | case LuaSymbolKind.class: return langserver_1.SymbolKind.Class; 16 | case LuaSymbolKind.table: return langserver_1.SymbolKind.Class; 17 | case LuaSymbolKind.module: return langserver_1.SymbolKind.Module; 18 | case LuaSymbolKind.property: return langserver_1.SymbolKind.Property; 19 | default: return langserver_1.SymbolKind.Variable; 20 | } 21 | } 22 | 23 | class SymbolProvider { 24 | constructor(coder) { 25 | this.coder = coder; 26 | this._isShow = (symbol) => { 27 | return (coder.settings.symbol.showAnonymousFunction || !symbol.name.includes("@")) 28 | && ((!coder.settings.symbol.showFunctionOnly || is.luaFunction(symbol.type))); 29 | } 30 | } 31 | 32 | provideDocumentSymbols(uri) { 33 | return awaiter.await(this, void 0, void 0, function* () { 34 | const mdl = LoadedPackages[uri]; 35 | if (!mdl) { 36 | return []; 37 | } 38 | 39 | let depth = 0, maxDepth = 5; //防止循环引用导致死循环 40 | let walker, collectAllChildren; 41 | walker = (def, collection) => { 42 | return awaiter.await(this, void 0, void 0, function* () { 43 | if (!(def instanceof LuaSymbol)) { 44 | return; 45 | } 46 | 47 | if (def.uri !== uri) { 48 | return; 49 | } 50 | 51 | if (depth++ >= maxDepth) { 52 | depth--; 53 | return; 54 | } 55 | 56 | if (!this._isShow(def)) { 57 | depth--; 58 | return; 59 | } 60 | 61 | const document = yield this.coder.document(def.uri); 62 | const RangeOf = (start, end) => { 63 | return langserver_1.Range.create(document.positionAt(start), document.positionAt(end)); 64 | } 65 | const symbol = langserver_1.DocumentSymbol.create( 66 | def.name, utils_2.symbolSignature(def), mapSymbolKind(def.kind), 67 | RangeOf(def.location[0], def.range[1]), RangeOf(def.location[0], def.location[1]), 68 | def.children 69 | ? yield collectAllChildren(def.children) 70 | : (is.luaTable(typeOf(def)) 71 | ? yield collectAllChildren(utils_1.object2Array(def.type.fields)) 72 | : void 0) 73 | ); 74 | 75 | collection.push(symbol); 76 | depth--; 77 | }); 78 | } 79 | 80 | collectAllChildren = (children) => { 81 | return awaiter.await(this, void 0, void 0, function* () { 82 | const collection = []; 83 | for (let i = 0; i < children.length; i++) { 84 | const child = children[i]; 85 | yield walker(child, collection); 86 | } 87 | return collection; 88 | }); 89 | } 90 | 91 | let symbols = yield collectAllChildren(mdl.children); 92 | return symbols; 93 | }); 94 | } 95 | }; 96 | 97 | exports.SymbolProvider = SymbolProvider; 98 | -------------------------------------------------------------------------------- /server/server-ipc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var server_1 = require('./server'); 3 | var langserver_1 = require('vscode-languageserver'); 4 | 5 | var connection = langserver_1.createConnection( 6 | new langserver_1.IPCMessageReader(process), new langserver_1.IPCMessageWriter(process)); 7 | server_1.server(connection); 8 | -------------------------------------------------------------------------------- /server/server-stdio.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var server_1 = require('./server'); 3 | var langserver_1 = require('vscode-languageserver'); 4 | 5 | var connection = langserver_1.createConnection(process.stdin, process.stdout); 6 | 7 | console.log = connection.console.log.bind(connection.console); 8 | console.error = connection.console.error.bind(connection.console); 9 | 10 | server_1.server(connection); 11 | 12 | process.stdin.on('close', () => { 13 | process.exit(0); 14 | }); 15 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 'use strict'; 6 | var coder_1 = require('./coder'); 7 | var protocols_1 = require('./protocols'); 8 | var langserver_1 = require('vscode-languageserver'); 9 | 10 | function server(connection) { 11 | var documents = new langserver_1.TextDocuments(); 12 | var coder = coder_1.instance(); 13 | 14 | connection.onInitialize((params) => { 15 | var ok = coder.init({ 16 | workspaceFolders: params.workspaceFolders || [], 17 | workspaceRoot: params.rootPath, 18 | connection: connection, 19 | documents: documents 20 | }); 21 | 22 | connection.console.info('extension initialize status: ' + ok); 23 | 24 | return { 25 | capabilities: { 26 | // Tell the client that the server works in FULL text document sync mode 27 | textDocumentSync: documents.syncKind, 28 | // Tell the client that the server support code complete 29 | documentSymbolProvider: true, 30 | definitionProvider: true, 31 | completionProvider: { 32 | resolveProvider: true, 33 | triggerCharacters: [".", ":", "'", '"', '/'] 34 | }, 35 | hoverProvider: true, 36 | signatureHelpProvider: { 37 | triggerCharacters: [',', '('] 38 | }, 39 | renameProvider: true, 40 | documentFormattingProvider: true, 41 | documentRangeFormattingProvider: true, 42 | // documentOnTypeFormattingProvider: { 43 | // firstTriggerCharacter: ";" 44 | // } 45 | // codeActionProvider: true 46 | } 47 | }; 48 | }); 49 | 50 | documents.onDidChangeContent((change) => { 51 | coder.onDidChangeContent(change); 52 | }); 53 | 54 | documents.onDidSave((params) => { 55 | coder.onDidSave(params); 56 | }); 57 | 58 | connection.onDidChangeConfiguration((change) => { 59 | coder.onDidChangeConfiguration(change); 60 | }); 61 | 62 | connection.onDidChangeWatchedFiles((change) => { 63 | coder.onDidChangeWatchedFiles(change); 64 | }); 65 | 66 | connection.onDocumentSymbol((params) => { 67 | return coder.provideDocumentSymbols(params); 68 | }); 69 | 70 | connection.onDefinition((params) => { 71 | return coder.provideDefinitions(params); 72 | }); 73 | 74 | connection.onCompletion((params) => { 75 | return coder.provideCompletions(params); 76 | }); 77 | 78 | connection.onCompletionResolve((item) => { 79 | return coder.resolveCompletion(item); 80 | }); 81 | 82 | connection.onHover((params) => { 83 | return coder.provideHover(params); 84 | }); 85 | 86 | connection.onSignatureHelp((params) => { 87 | return coder.provideSignatureHelp(params); 88 | }) 89 | 90 | connection.onRenameRequest(params => { 91 | return coder.provideRename(params); 92 | }); 93 | 94 | connection.onDocumentFormatting(params => { 95 | return coder.formatDocument(params); 96 | }); 97 | 98 | connection.onDocumentRangeFormatting(params => { 99 | return coder.formatDocument(params); 100 | }); 101 | 102 | connection.onDocumentOnTypeFormatting(params => { 103 | coder.tracer.info('onDocumentOnTypeFormatting'); 104 | return coder.formatOnTyping(params); 105 | }); 106 | 107 | connection.onRequest(protocols_1.LDocRequest.type, (params) => { 108 | return coder.onLDocRequest(params); 109 | }); 110 | 111 | connection.onRequest(protocols_1.BustedRequest.type, (params) => { 112 | return coder.onBustedRequest(params); 113 | }); 114 | 115 | connection.onCodeAction((params) => { 116 | return undefined; 117 | }); 118 | 119 | connection.onExit(() => { 120 | }); 121 | 122 | documents.onDidClose(event => { 123 | return coder.onDidClosed(event.document); 124 | }); 125 | 126 | documents.listen(connection); 127 | // Listen on the connection 128 | connection.listen(); 129 | } 130 | 131 | exports.server = server; 132 | -------------------------------------------------------------------------------- /server/tracer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Tracer { 4 | constructor() { 5 | this.coder = undefined; 6 | } 7 | 8 | init(coder) { 9 | this.coder = coder; 10 | } 11 | 12 | enabled() { 13 | return this.coder && this.coder.initialized() && this.coder.settings.debug; 14 | } 15 | 16 | info(msg) { 17 | if (this.enabled()) { 18 | this.coder.conn.console.info(msg); 19 | } 20 | } 21 | 22 | warn(msg) { 23 | if (this.enabled()) { 24 | this.coder.conn.console.warn(msg); 25 | } 26 | } 27 | 28 | error(msg) { 29 | if (this.enabled()) { 30 | this.coder.conn.console.error(msg); 31 | } 32 | } 33 | } 34 | 35 | var _instance = undefined; 36 | exports.instance = function () { 37 | if (_instance === undefined) { 38 | _instance = new Tracer(); 39 | } 40 | 41 | return _instance; 42 | }; -------------------------------------------------------------------------------- /snippets/ldoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@doc": { 3 | "body": "--- ${1:Summary ends with a period.}\n-- ${2:Some description, can be over several lines.}", 4 | "description": "ldoc: description of a function", 5 | "prefix": "@doc", 6 | "scope": "source.lua" 7 | }, 8 | "@module": { 9 | "body": "--- ${1:module description}\n-- @module ${2:module name}", 10 | "description": "ldoc: document a module", 11 | "prefix": "@module", 12 | "scope": "source.lua" 13 | }, 14 | "@function": { 15 | "body": "-- @function ${1:function-name}", 16 | "description": "ldoc: mark function name", 17 | "prefix": "@function", 18 | "scope": "source.lua" 19 | }, 20 | "@param": { 21 | "body": "-- @param ${1:name} ${2:description}", 22 | "description": "ldoc: document a parameter", 23 | "prefix": "@param", 24 | "scope": "source.lua" 25 | }, 26 | "@return": { 27 | "body": "-- @return ${1:name} ${2:description}", 28 | "description": "ldoc: document a return value", 29 | "prefix": "@return", 30 | "scope": "source.lua" 31 | }, 32 | "@author": { 33 | "body": "-- @author ${1:the author}", 34 | "description": "ldoc: author of the module/function", 35 | "prefix": "@author", 36 | "scope": "source.lua" 37 | }, 38 | "@raise": { 39 | "body": "-- @raise ${1:raise error description}", 40 | "description": "ldoc: unhandled error thrown by this function", 41 | "prefix": "@raise", 42 | "scope": "source.lua" 43 | }, 44 | "@local": { 45 | "body": "-- @local ${1:function-name}", 46 | "description": "ldoc: explicitly marks a function as not being exported", 47 | "prefix": "@local", 48 | "scope": "source.lua" 49 | }, 50 | "@see": { 51 | "body": "-- @see ${1:the reference name}", 52 | "description": "ldoc: reference other documented items", 53 | "prefix": "@see", 54 | "scope": "source.lua" 55 | }, 56 | "@usage": { 57 | "body": "-- @usage ${1:the example code}", 58 | "description": "ldoc: give an example of a function's use", 59 | "prefix": "@usage", 60 | "scope": "source.lua" 61 | }, 62 | "@table": { 63 | "body": "-- @table ${1:table-name}", 64 | "description": "ldoc: mark a lua table", 65 | "prefix": "@table", 66 | "scope": "source.lua" 67 | }, 68 | "@field": { 69 | "body": "-- @field ${1:field-name}", 70 | "description": "ldoc: a named member of a table", 71 | "prefix": "@field", 72 | "scope": "source.lua" 73 | }, 74 | "@section": { 75 | "body": "-- @section ${1:section name}", 76 | "description": "ldoc: starting a named section for grouping functions or tables together", 77 | "prefix": "@section", 78 | "scope": "source.lua" 79 | }, 80 | "@type": { 81 | "body": "-- @type ${1:name}", 82 | "description": "ldoc: a section which describes a class", 83 | "prefix": "@type", 84 | "scope": "source.lua" 85 | }, 86 | "@todo": { 87 | "body": "-- @todo ${1:description}", 88 | "description": "ldoc: description of task todo", 89 | "prefix": "@todo", 90 | "scope": "source.lua" 91 | }, 92 | "@warning": { 93 | "body": "-- @warning ${1:description}", 94 | "description": "ldoc: something to pay attention", 95 | "prefix": "@warning", 96 | "scope": "source.lua" 97 | }, 98 | "@tparam": { 99 | "body": "-- @tparam ${1:type} ${2:name} ${3:description}", 100 | "description": "ldoc: parameter with type specific", 101 | "prefix": "@tparam", 102 | "scope": "source.lua" 103 | }, 104 | "@treturn": { 105 | "body": "-- @treturn ${1:type} ${2:name} ${3:description}", 106 | "description": "ldoc: return value with type specific", 107 | "prefix": "@treturn", 108 | "scope": "source.lua" 109 | } 110 | } -------------------------------------------------------------------------------- /snippets/lua.json: -------------------------------------------------------------------------------- 1 | { 2 | "if": { 3 | "description": "insert if statement", 4 | "prefix": "if", 5 | "scope": "source.lua", 6 | "body": "if ${1:condition} then\n\t${2:-- statements}\nend" 7 | }, 8 | "ifelse": { 9 | "description": "insert if-else statement", 10 | "prefix": "ifelse", 11 | "scope": "source.lua", 12 | "body": "if ${1:condition} then\n\t${2:-- statements}\nelse\n\t${3:-- statements}\nend" 13 | }, 14 | "elseif": { 15 | "description": "insert elseif statement", 16 | "prefix": "elseif", 17 | "scope": "source.lua", 18 | "body": "elseif ${1:condition} then\n\t${2:-- statements}\n" 19 | }, 20 | "local": { 21 | "description": "insert local variable statement", 22 | "prefix": "local", 23 | "scope": "source.lua", 24 | "body": "local ${1:name} = ${2:value}\n" 25 | }, 26 | "localfunc": { 27 | "description": "insert local function statement", 28 | "prefix": "localfunc", 29 | "scope": "source.lua", 30 | "body": "local function ${1:name}(${2:params})\n\t${3:-- statements}\nend" 31 | }, 32 | "func": { 33 | "description": "insert function statement", 34 | "prefix": "func", 35 | "scope": "source.lua", 36 | "body": "function ${1:name}(${2:params})\n\t${3:-- statements}\nend" 37 | }, 38 | "for": { 39 | "description": "insert for loop statement", 40 | "prefix": "for", 41 | "scope": "source.lua", 42 | "body": "for ${1:i} = ${2:1}, ${3:10}, ${4:step} do\n\t${5:-- statements}\nend" 43 | }, 44 | "forp": { 45 | "description": "insert for loop statement", 46 | "prefix": "forp", 47 | "scope": "source.lua", 48 | "body": "for ${1:key}, ${2:value} in pairs(${3:table}) do\n\t${4:-- statements}\nend" 49 | }, 50 | "fori": { 51 | "description": "insert for loop statement", 52 | "prefix": "fori", 53 | "scope": "source.lua", 54 | "body": "for ${1:index}, ${2:value} in ipairs(${3:table}) do\n\t${4:-- statements}\nend" 55 | }, 56 | "while": { 57 | "description": "insert while loop statement", 58 | "prefix": "while", 59 | "scope": "source.lua", 60 | "body": "while ${1:condition} do\n\t${2:-- statements}\nend" 61 | }, 62 | "do": { 63 | "description": "insert do statement", 64 | "prefix": "do", 65 | "scope": "source.lua", 66 | "body": "do\n\t${1:-- statements}\nend" 67 | }, 68 | "ret": { 69 | "description": "insert return statement", 70 | "prefix": "ret", 71 | "scope": "source.lua", 72 | "body": "return ${1:statements}" 73 | }, 74 | "repeat": { 75 | "description": "insert repeat statement", 76 | "prefix": "repeat", 77 | "scope": "source.lua", 78 | "body": "repeat\n\t${1:-- statements}\nuntil ${2:condition}" 79 | }, 80 | "true": { 81 | "description": "insert boolean value true", 82 | "prefix": "true", 83 | "scope": "source.lua", 84 | "body": "true" 85 | }, 86 | "false": { 87 | "description": "insert boolean value false", 88 | "prefix": "false", 89 | "scope": "source.lua", 90 | "body": "false" 91 | }, 92 | "nil": { 93 | "description": "insert nil", 94 | "prefix": "nil", 95 | "scope": "source.lua", 96 | "body": "nil" 97 | }, 98 | "and": { 99 | "description": "insert and", 100 | "prefix": "and", 101 | "scope": "source.lua", 102 | "body": "and" 103 | }, 104 | "or": { 105 | "description": "insert or", 106 | "prefix": "or", 107 | "scope": "source.lua", 108 | "body": "or" 109 | }, 110 | "not": { 111 | "description": "insert not", 112 | "prefix": "not", 113 | "scope": "source.lua", 114 | "body": "not" 115 | }, 116 | "then": { 117 | "description": "insert then", 118 | "prefix": "then", 119 | "scope": "source.lua", 120 | "body": "then" 121 | } 122 | } -------------------------------------------------------------------------------- /test/test.engine.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const engine = require('../server/lib/engine'); 4 | 5 | class Logger { 6 | constructor() { } 7 | error(msg) { 8 | console.log('error: ' + msg); 9 | } 10 | 11 | warn(msg) { 12 | console.log('warn:' + msg); 13 | } 14 | }; 15 | 16 | //tester 17 | fs.readFile('./test/textures/test01.lua', (err, data) => { 18 | let uri = './textures/test01.lua'; 19 | engine.parseDocument(data, uri, new Logger()); 20 | let x = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('CPubClass', [591, 604], uri))[0]); 21 | console.log(x); 22 | console.log(x.get('name')); 23 | console.log(x.get('new')); 24 | 25 | console.log(engine.completionProvider(new engine.CompletionContext('CPubClass.', [591, 604], uri))); 26 | console.log(engine.completionProvider(new engine.CompletionContext('CPubClass.base:', [591, 604], uri))) 27 | 28 | let xy = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('xy', [384, 385], uri))[0]); 29 | console.log(xy); 30 | let xz = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('xy', [999, 1000], uri))[0]); 31 | console.log(xz); 32 | 33 | let n1 = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('n1', [999, 1001], uri))[0]); 34 | let n2 = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('n2', [999, 1001], uri))[0]); 35 | console.log(n1); 36 | console.log(n2); 37 | 38 | let nb = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('nb', [999, 1001], uri))[0]); 39 | console.log(nb); 40 | 41 | let xt = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('xt', [1999, 2001], uri))[0]); 42 | console.log(xt); 43 | 44 | let isnumber = engine.typeOf(engine.definitionProvider(new engine.DefinitionContext('isnumber', [1999, 2008], uri))[0]); 45 | console.log(engine.typeOf(isnumber.returns[0])); 46 | 47 | let execute = engine.definitionProvider(new engine.DefinitionContext('_os.execute', [1999, 2001], uri))[0]; 48 | console.log(execute); 49 | }); 50 | 51 | console.log(void 0 === null); -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isalpha(c) { 4 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 5 | } 6 | 7 | function isdigit(c) { 8 | return c >= '0' && c <= '9'; 9 | } 10 | 11 | function skip(pattern, content, offset, step) { 12 | while (pattern.test(content.charAt(offset))) { 13 | offset += step; 14 | } 15 | return offset; 16 | } 17 | 18 | function backward(content, offset, collection) { 19 | let bracketDepth = 0; 20 | 21 | while (true) { 22 | let c = content.charAt(offset); 23 | if (c === '.' || c === ':') { 24 | if (bracketDepth === 0) { 25 | collection.push(c); 26 | } 27 | offset--; 28 | offset = skip(/\s/, content, offset, -1); 29 | continue; 30 | } 31 | 32 | if (c === ')') { 33 | bracketDepth++; 34 | offset--; 35 | continue; 36 | } 37 | 38 | if (c === '(') { 39 | bracketDepth--; 40 | if (bracketDepth < 0) { 41 | return; 42 | } 43 | offset--; 44 | continue; 45 | } 46 | 47 | if (isalpha(c) || isdigit(c) || c === '_') { 48 | offset--; 49 | if (bracketDepth === 0) { 50 | collection.push(c); 51 | } 52 | continue; 53 | } 54 | 55 | if (c === ' ' || c === ',') { 56 | if (bracketDepth === 0) { 57 | break; 58 | } 59 | offset--; 60 | continue; 61 | } 62 | 63 | break; 64 | } 65 | 66 | collection.reverse(); 67 | return offset + 1; 68 | } 69 | 70 | let testStr = `return x, base.Class:new().calc(x, y, z(xyz)) 71 | .check() 72 | .` 73 | let collection = []; 74 | console.log(testStr.substring(backward(testStr, testStr.length - 1, collection))); 75 | console.log(collection.join('')); 76 | 77 | let a = [1, 2] 78 | let b = [1, 2] 79 | console.log(a == b) -------------------------------------------------------------------------------- /test/textures/rr.lua: -------------------------------------------------------------------------------- 1 | for k, v in pairs(coroutine) do 2 | print(k, v) 3 | end 4 | 5 | local xx = function(x) 6 | local y = x + 1 7 | return function() 8 | return y + 1 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/textures/std.json: -------------------------------------------------------------------------------- 1 | { 2 | "io": { 3 | "name": "io", 4 | "type": "class", 5 | "desc": "The I/O library provides two different styles for file manipulation. The first one uses implicit file handles; that is, there are operations to set a default input file and a default output file, and all input/output operations are over these default files. The second style uses explicit file handles.", 6 | "detail": "http://www.lua.org/manual/5.3/manual.html#6.8", 7 | "fields": { 8 | "close": { 9 | "name": "close", 10 | "type": "function", 11 | "desc": "Equivalent to `file:close()`. Without a `file`, closes the default output file.", 12 | "detail": "http://www.lua.org/manual/5.3/manual.html#pdf-io.close", 13 | "args": [ 14 | { 15 | "name": "handle", 16 | "type": "parameter" 17 | } 18 | ], 19 | "returns": [] 20 | }, 21 | "flush": { 22 | "name": "flush", 23 | "type": "function", 24 | "desc": "Equivalent to `io.output():flush()`.", 25 | "detail": "http://www.lua.org/manual/5.3/manual.html#pdf-io.flush", 26 | "args": [], 27 | "returns": [] 28 | }, 29 | "input": { 30 | "name": "input", 31 | "type": "function", 32 | "desc": "When called with a file name, it opens the named file (in text mode), and sets its handle as the default input file. When called with a file handle, it simply sets this file handle as the default input file. When called without arguments, it returns the current default input file.", 33 | "detail": "http://www.lua.org/manual/5.3/manual.html#pdf-io.input", 34 | "args": [ 35 | { 36 | "name": "file", 37 | "type": "parameter" 38 | } 39 | ], 40 | "returns": [] 41 | }, 42 | "lines": { 43 | "name": "lines", 44 | "type": "function", 45 | "desc": "Opens the given file name in read mode and returns an iterator function that works like `file:lines(···)` over the opened file. When the iterator function detects the end of file, it returns no values (to finish the loop) and automatically closes the file.", 46 | "detail": "http://www.lua.org/manual/5.3/manual.html#pdf-io.lines", 47 | "args": [ 48 | { 49 | "name": "filename", 50 | "type": "parameter" 51 | }, 52 | { 53 | "name": "...", 54 | "type": "parameter" 55 | } 56 | ], 57 | "returns": [ 58 | { 59 | "name": "iterator", 60 | "type": "class", 61 | "fields": {} 62 | } 63 | ] 64 | }, 65 | "open": { 66 | "name": "open", 67 | "type": "function", 68 | "desc": "This function opens a file, in the mode specified in the string `mode`. In case of success, it returns a new file handle.", 69 | "detail": "http://www.lua.org/manual/5.3/manual.html#pdf-io.open", 70 | "args": [ 71 | { 72 | "name": "filename", 73 | "type": "parameter" 74 | }, 75 | { 76 | "name": "mode", 77 | "type": "parameter" 78 | } 79 | ], 80 | "returns": [ 81 | { 82 | "name": "file", 83 | "type": "class" 84 | } 85 | ] 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /test/textures/test01.lua: -------------------------------------------------------------------------------- 1 | module('test01', package.seeall) 2 | 3 | local stdlib = require('lib.stdlib') 4 | 5 | CPubClass = { 6 | name = 'CPubClass', 7 | new = function() 8 | return CPubClass 9 | end 10 | } 11 | 12 | function CPubClass.abc(x, y) 13 | return x * y * 2 14 | end 15 | 16 | CPubClass.base = { 17 | get = function() 18 | -- body 19 | end 20 | } 21 | function CPubClass.base:print() 22 | end 23 | 24 | if CPubClass == {} then 25 | local xy = 1 26 | 27 | load('') 28 | end 29 | 30 | for index = 1, 10 do 31 | print(index) 32 | local zz = 'hello world' 33 | local foo = zz .. ' sss' 34 | end 35 | 36 | local n1 = 1 37 | local n2 = n1 + 2 38 | 39 | local nb = n1 or (n1 + 2) 40 | 41 | local xt = CPubClass.new(nb) 42 | 43 | function isnumber(s) 44 | local xt = 1 45 | return s(xt) == 1 46 | end 47 | 48 | local handle = io.open('.') 49 | --------------------------------------------------------------------------------