├── .gitignore ├── LICENSE ├── README.md ├── agent ├── tsplugin.js └── tsplugin.ts ├── dist ├── index.d.ts ├── index.js ├── infoprovider.d.ts ├── infoprovider.js ├── logger.d.ts └── logger.js ├── example.png ├── package.json ├── src ├── index.ts ├── infoprovider.ts └── logger.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 recat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # frida-tsplugin 3 | AutoComplete plugin for frida's java warpper. 4 | 5 | ![](./example.png) 6 | 7 | # Usage 8 | ## install 9 | ``` 10 | $ git clone https://github.com/tacesrever/frida-tsplugin.git 11 | $ cd frida-tsplugin 12 | $ npm install 13 | ``` 14 | ## load 15 | with frida ^14.0.0: 16 | ``` 17 | $ frida -U -l agent/tsplugin.js target 18 | $ adb forward tcp:28042 tcp:28042 19 | ``` 20 | or you can edit the port in agent/tsplugin.ts and compile it by frida-compile. 21 | 22 | add plugin in `frida-agent-example/tsconfig.json` after setup [frida-agent-example](https://github.com/oleavr/frida-agent-example): 23 | 24 | ``` 25 | { 26 | "compilerOptions": { 27 | ... 28 | , 29 | "plugins": [{ 30 | "name": path to tsplugin, 31 | "host"?: ip of target device, default is "127.0.0.1"(use adb forward tcp:port tcp:port) 32 | "port"?: listen port in tsplugin.ts, default is 28042 33 | "logfile"?: path to logfile 34 | }] 35 | } 36 | } 37 | ``` 38 | ## debug 39 | 40 | if nothing happen after load, you can: 41 | - Ensure plugin is loaded 42 | > set logfile path mentioned above, if the logfile didn't created, the plugin may fail to load. 43 | > press F1, type 'Ty' then click Typescript: Open TS Server log, find 'frida-tsplugin' to see if plguin load sucessed. 44 | > 45 | - Ensure frida-tsplugin's typescript version same as vscode's. 46 | > check typescript version in vscode's install dir `Microsoft VS Code( Insiders)/resources/app/extensions/node_modules/typescript/package.json` and in frida-tsplugin/package.json's dependencies, if not same, you should run `npm i typescript@version from vscode` then `tsc -p .` under frida-tsplugin. 47 | - Ensure agent service is on 48 | > open http://127.0.0.1:28042/getJavaClassInfo?className=java.lang.String to see if any content. The target app may need be in the foreground with phone's screen on for service to response. -------------------------------------------------------------------------------- /agent/tsplugin.ts: -------------------------------------------------------------------------------- 1 | // compile this use frida-compile 2 | import * as http from 'http'; 3 | import * as url from 'url'; 4 | import * as qs from 'querystring'; 5 | 6 | const listenPort = 28042; 7 | 8 | const routeMap: { 9 | [index: string]: http.RequestListener 10 | }= {}; 11 | 12 | const server = http.createServer(function(req, res) { 13 | const uri = url.parse(req.url); 14 | const handler = routeMap[uri.pathname]; 15 | if(handler) { 16 | handler(req, res); 17 | } else { 18 | res.writeHead(404); 19 | res.write("404 not found"); 20 | res.end(); 21 | } 22 | }); 23 | 24 | if(Java.available) { 25 | let wrapperProps: string[] = []; 26 | Java.perform(() => { 27 | const JavaString = Java.use("java.lang.String"); 28 | let prototype = JavaString.__proto__; 29 | while(prototype.__proto__ !== null) { 30 | wrapperProps = wrapperProps.concat(Object.getOwnPropertyNames(prototype)); 31 | prototype = prototype.__proto__; 32 | } 33 | }); 34 | 35 | interface getJavaClassInfoParams { 36 | className: string 37 | } 38 | interface JavaMethodInfo { 39 | returnType: string 40 | argumentTypes: string[] 41 | } 42 | interface JavaClassInfo { 43 | alltypes: string[] 44 | fields: { 45 | [index: string]: string 46 | } 47 | methods: { 48 | [index: string]: JavaMethodInfo[] 49 | } 50 | wrapperProps: string[] 51 | } 52 | routeMap["/getJavaClassInfo"] = function(req, res) { 53 | const uri = url.parse(req.url); 54 | const query = qs.parse(uri.query) as any as getJavaClassInfoParams; 55 | Java.perform(() => { 56 | let wrapper: Java.Wrapper; 57 | try { 58 | wrapper = Java.use(query.className); 59 | } catch { 60 | res.writeHead(404); 61 | res.end(); 62 | return; 63 | } 64 | try { 65 | const classInfo: JavaClassInfo = { 66 | alltypes: [], 67 | fields: {}, 68 | methods: {}, 69 | wrapperProps: wrapperProps 70 | } 71 | Object.getOwnPropertyNames(wrapper).forEach(propname => { 72 | if((wrapper as any)[propname].fieldReturnType !== undefined) { 73 | classInfo.fields[propname] = (wrapper as any)[propname].fieldReturnType.className; 74 | } else { 75 | classInfo.methods[propname] = []; 76 | (wrapper as any)[propname].overloads.forEach(m => { 77 | classInfo.methods[propname].push({ 78 | returnType: m.returnType.className, 79 | argumentTypes: m.argumentTypes.map(type => { 80 | return type.className; 81 | }) 82 | }); 83 | }); 84 | } 85 | }); 86 | let klass = wrapper.class; 87 | while(klass !== null) { 88 | classInfo.alltypes.push(klass.getName()); 89 | klass = klass.getSuperclass(); 90 | } 91 | wrapper.class.getInterfaces().forEach(iface => { 92 | classInfo.alltypes.push(iface.getName()); 93 | }); 94 | const constructorInfo: JavaMethodInfo[] = []; 95 | wrapper.class.getConstructors().forEach(method => { 96 | constructorInfo.push({ 97 | argumentTypes: method.getParameterTypes().map(type => type.getName()), 98 | returnType: classInfo.alltypes[0] 99 | }); 100 | }); 101 | classInfo.methods["$new"] = constructorInfo; 102 | res.writeHead(200); 103 | res.write(JSON.stringify(classInfo)); 104 | res.end(); 105 | return; 106 | } catch(e) { 107 | console.log(e); 108 | res.writeHead(500); 109 | res.end(); 110 | } 111 | }); 112 | } 113 | server.listen(listenPort); 114 | } -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as tslib from 'typescript/lib/tsserverlibrary'; 2 | declare function init(mod: { 3 | typescript: typeof tslib; 4 | }): { 5 | create: (info: tslib.server.PluginCreateInfo) => tslib.LanguageService; 6 | }; 7 | export = init; 8 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const tslib = require("typescript/lib/tsserverlibrary"); 3 | const infoprovider_1 = require("./infoprovider"); 4 | const logger_1 = require("./logger"); 5 | function init(mod) { 6 | const typescript = mod.typescript; 7 | function create(info) { 8 | const tsLS = info.languageService; 9 | if (info.config.logfile !== undefined) 10 | (0, logger_1.setLogfile)(info.config.logfile); 11 | const proxy = Object.create(null); 12 | for (let k of Object.keys(info.languageService)) { 13 | const x = info.languageService[k]; 14 | proxy[k] = (...args) => x.apply(info.languageService, args); 15 | } 16 | const javaLoader = new infoprovider_1.JavaProviderLoader(info.config); 17 | proxy.getCompletionsAtPosition = (fileName, position, options) => { 18 | const source = getSourceFile(fileName); 19 | let oret = tsLS.getCompletionsAtPosition(fileName, position, options); 20 | try { 21 | const completeFor = getNodeAtPosition(source, position).parent.getChildAt(0); 22 | (0, logger_1.log)("trigger:", options.triggerCharacter, 'completeFor ' + completeFor.getText()); 23 | const provider = findInfoProviderForExpr(source, completeFor); 24 | if (provider === undefined) 25 | return oret; 26 | const entries = provider.getCompletionEntries(oret ? oret.entries : undefined); 27 | if (entries === undefined) 28 | return oret; 29 | if (oret === undefined) { 30 | oret = { 31 | entries: entries, 32 | isGlobalCompletion: false, 33 | isMemberCompletion: true, 34 | isNewIdentifierLocation: false 35 | }; 36 | } 37 | else { 38 | oret.entries = entries; 39 | } 40 | } 41 | catch (e) { 42 | (0, logger_1.log)(e.stack); 43 | } 44 | return oret; 45 | }; 46 | proxy.getCompletionEntryDetails = (fileName, position, name, options, source, pref, data) => { 47 | (0, logger_1.log)("getCompletionEntryDetails", name, source); 48 | if (source && source.indexOf("Java_") === 0) { 49 | let [type, className] = source.substr(5).split(':'); 50 | if (type === 'c') { 51 | const provider = javaLoader.getProviderByName(className); 52 | const details = provider.getCompletionDetail(name); 53 | if (details !== undefined) 54 | return details; 55 | } 56 | else { 57 | const divpos = className.lastIndexOf('.'); 58 | const propName = className.substr(divpos + 1); 59 | className = className.substr(0, divpos); 60 | const provider = javaLoader.getProviderByName(className); 61 | const propProvider = provider.getPropInfoProvider(propName); 62 | const details = propProvider.getCompletionDetail(name); 63 | if (details !== undefined) 64 | return details; 65 | } 66 | } 67 | return tsLS.getCompletionEntryDetails(fileName, position, name, options, source, pref, data); 68 | }; 69 | proxy.getQuickInfoAtPosition = (fileName, position) => { 70 | const info = tsLS.getQuickInfoAtPosition(fileName, position); 71 | const source = getSourceFile(fileName); 72 | try { 73 | let getInfoFor = getNodeAtPosition(source, position); 74 | if (getInfoFor.parent.kind === tslib.SyntaxKind.PropertyAccessExpression 75 | && getInfoFor.parent.getChildAt(0) !== getInfoFor) { 76 | getInfoFor = getInfoFor.parent; 77 | } 78 | const provider = findInfoProviderForExpr(source, getInfoFor); 79 | if (provider === undefined) 80 | return info; 81 | // get info for overload function 82 | if (getInfoFor.parent.kind === tslib.SyntaxKind.CallExpression 83 | && getInfoFor.parent.getChildAt(0) === getInfoFor) { 84 | const callExpr = getInfoFor.parent; 85 | const argTypes = findArgTypesForCallExpr(source, callExpr); 86 | if (argTypes === undefined) 87 | return info; 88 | info.displayParts = [{ 89 | text: provider.getDeclare(argTypes), 90 | kind: 'text' 91 | }]; 92 | return info; 93 | } 94 | info.displayParts = [{ 95 | text: provider.getDeclare(), 96 | kind: 'text' 97 | }]; 98 | } 99 | catch (e) { 100 | (0, logger_1.log)(e); 101 | } 102 | return info; 103 | }; 104 | function getSourceFile(fileName) { 105 | return tsLS.getNonBoundSourceFile(fileName); 106 | } 107 | function getNodeAtPosition(source, position) { 108 | let current = source; 109 | outer: while (true) { 110 | for (const child of current.getChildren(source)) { 111 | const start = child.getFullStart(); 112 | if (start > position) { 113 | return current; 114 | } 115 | const end = child.getEnd(); 116 | if (position <= end) { 117 | current = child; 118 | continue outer; 119 | } 120 | } 121 | return current; 122 | } 123 | } 124 | function findInfoProviderForExpr(source, node) { 125 | let current = node; 126 | (0, logger_1.log)("findInfoProviderForExpr", node.getText(), node.kind); 127 | while (true) { 128 | switch (current.kind) { 129 | case tslib.SyntaxKind.CallExpression: 130 | return findReturnInfoProviderForCallExpr(source, current); 131 | case tslib.SyntaxKind.Identifier: 132 | let writeRef = findLastWriteRef(source.fileName, current.getStart()); 133 | let typeName = writeRef.definition.name.split(':')[1]; 134 | if (typeName !== undefined) { 135 | typeName = typeName.trim(); 136 | if (typeName !== 'any' && typeName.indexOf('Java.Wrapper') !== 0) { 137 | (0, logger_1.log)("type missmatch:", typeName); 138 | return undefined; 139 | } 140 | } 141 | let writeExpr = getNodeAtPosition(source, writeRef.reference.textSpan.start + 1).parent; 142 | if (writeRef.definition.kind === tslib.ScriptElementKind.parameterElement) { 143 | current = writeExpr; 144 | break; 145 | } 146 | while (writeExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 147 | writeExpr = writeExpr.parent; 148 | if (![ 149 | tslib.SyntaxKind.BinaryExpression, 150 | tslib.SyntaxKind.PropertyAssignment, 151 | tslib.SyntaxKind.VariableDeclaration, 152 | ].includes(writeExpr.kind)) { 153 | return undefined; 154 | } 155 | current = writeExpr.getChildAt(2); 156 | break; 157 | case tslib.SyntaxKind.ElementAccessExpression: 158 | case tslib.SyntaxKind.PropertyAccessExpression: 159 | let parentNode = current.getChildAt(0); 160 | let propNode = current.getChildAt(2); 161 | let propWriteRef = undefined; 162 | let propName = propNode.getText(); 163 | if (!["value", "$new", "$init", "overload"].includes(propName)) 164 | propWriteRef = findLastWriteRef(source.fileName, propNode.getEnd()); 165 | if (propWriteRef === undefined) { 166 | let provider = findInfoProviderForExpr(source, parentNode); 167 | if (provider === undefined) { 168 | return undefined; 169 | } 170 | return provider.getPropInfoProvider(propNode.getText()); 171 | } 172 | let tmpExpr = getNodeAtPosition(source, propWriteRef.reference.textSpan.start).parent; 173 | if (tmpExpr.kind === tslib.SyntaxKind.PropertyAssignment) { 174 | current = tmpExpr.getChildAt(2); 175 | break; 176 | } 177 | while (tmpExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 178 | tmpExpr = tmpExpr.parent; 179 | if (tmpExpr.kind === tslib.SyntaxKind.BinaryExpression) { 180 | current = tmpExpr.getChildAt(2); 181 | break; 182 | } 183 | return undefined; 184 | // parse this and params from: 185 | // func.impl = (...args) 186 | // TODO: Java.choose => onMatch: function (instance) 187 | case tslib.SyntaxKind.ThisKeyword: 188 | case tslib.SyntaxKind.Parameter: 189 | let target = current; 190 | while (![tslib.SyntaxKind.FunctionExpression, 191 | tslib.SyntaxKind.FunctionDeclaration, 192 | tslib.SyntaxKind.ArrowFunction] 193 | .includes(current.kind)) 194 | current = current.parent; 195 | let funcDefExpr = current; 196 | let funcAssignExpr; 197 | if (funcDefExpr.name !== undefined) { 198 | // TODO: for named function, find use of it 199 | return undefined; 200 | } 201 | else { 202 | funcAssignExpr = funcDefExpr.parent; 203 | } 204 | if (![ 205 | tslib.SyntaxKind.BinaryExpression, 206 | tslib.SyntaxKind.PropertyAssignment, 207 | ].includes(funcAssignExpr.kind)) 208 | return undefined; 209 | const leftValue = funcAssignExpr.getChildAt(0); 210 | if (leftValue.kind === tslib.SyntaxKind.PropertyAccessExpression 211 | && leftValue.getChildAt(2).getText() === 'implementation') { 212 | let methodNode = leftValue.getChildAt(0); 213 | if (target.kind === tslib.SyntaxKind.ThisKeyword) { 214 | if (methodNode.kind === tslib.SyntaxKind.CallExpression && 215 | methodNode.getChildAt(0).getChildAt(2).getText() === 'overload') { 216 | methodNode = methodNode.getChildAt(0).getChildAt(0); 217 | } 218 | let classNode = methodNode.getChildAt(0); 219 | return findInfoProviderForExpr(source, classNode); 220 | } 221 | // is parameter 222 | let i; 223 | for (i = 0; i < funcDefExpr.parameters.length; ++i) { 224 | if (funcDefExpr.parameters[i] === target) { 225 | break; 226 | } 227 | } 228 | if (methodNode.kind === tslib.SyntaxKind.CallExpression 229 | && methodNode.getChildAt(0).getChildAt(2).getText() === 'overload') { 230 | const argTypeNameNode = methodNode.arguments[i]; 231 | const argTypeName = findStringLiteral(source, argTypeNameNode); 232 | if (argTypeName === undefined) 233 | return undefined; 234 | return javaLoader.getProviderByName(argTypeName); 235 | } 236 | const method = findInfoProviderForExpr(source, methodNode); 237 | if (method === undefined) 238 | return undefined; 239 | return javaLoader.getProviderByName(method.getParamClassNames()[i]); 240 | } 241 | default: 242 | return undefined; 243 | } 244 | } 245 | } 246 | function findReturnInfoProviderForCallExpr(source, callExpr) { 247 | let funcExpr = callExpr.expression; 248 | if (funcExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) { 249 | let funcPropAccExpr = funcExpr; 250 | let funcName = funcPropAccExpr.name.text; 251 | if (funcPropAccExpr.expression.getText() === "Java") { 252 | if (funcName === 'use') { 253 | return javaLoader.getProviderByName(findStringLiteral(source, callExpr.arguments[0])); 254 | } 255 | if (funcName === 'cast') { 256 | let classNode = callExpr.arguments[1]; 257 | if (classNode === undefined) 258 | return undefined; 259 | return findInfoProviderForExpr(source, classNode); 260 | } 261 | return undefined; 262 | } 263 | let parent = findInfoProviderForExpr(source, funcPropAccExpr.expression); 264 | if (parent === undefined) 265 | return undefined; 266 | if (funcName === 'call') { 267 | let method = parent; 268 | let argTypes = findArgTypesForCallExpr(source, callExpr); 269 | if (argTypes === undefined) 270 | return undefined; 271 | argTypes = argTypes.slice(1); 272 | return method.getOverloadInfoProvider(argTypes).getReturnInfoProvider(); 273 | } 274 | else if (funcName === 'apply') { 275 | let method = parent; 276 | // TODO: detact apply argTypes 277 | return method.getReturnInfoProvider(); 278 | } 279 | else if (funcName === 'overload') { 280 | let method = parent; 281 | let argTypes = callExpr.arguments.map(expr => findStringLiteral(source, expr)); 282 | if (argTypes === undefined) 283 | return undefined; 284 | return method.getOverloadInfoProvider(argTypes); 285 | } 286 | else { 287 | let klass = parent; 288 | let method = klass.getMethodInfoProvider(funcName); 289 | if (!method.hasOverload()) 290 | return method.getReturnInfoProvider(); 291 | let argTypes = findArgTypesForCallExpr(source, callExpr); 292 | if (argTypes === undefined) 293 | return undefined; 294 | return method.getReturnInfoProvider(argTypes); 295 | } 296 | } 297 | let method = findInfoProviderForExpr(source, funcExpr); 298 | if (method === undefined) 299 | return undefined; 300 | return method.getReturnInfoProvider(); 301 | } 302 | function findArgTypesForCallExpr(source, callExpr) { 303 | let argTypes = []; 304 | for (let i = 0; i < callExpr.arguments.length; ++i) { 305 | let arg = callExpr.arguments[i]; 306 | let typeName = findTargetTypeForCommonType(source, arg); 307 | if (typeName === undefined) { 308 | let type = findInfoProviderForExpr(source, arg); 309 | if (type === undefined) 310 | return undefined; 311 | typeName = type.getClassName(); 312 | } 313 | argTypes.push(typeName); 314 | } 315 | return argTypes; 316 | } 317 | function findTargetTypeForCommonType(source, node) { 318 | switch (node.kind) { 319 | case tslib.SyntaxKind.NumericLiteral: 320 | return 'int'; 321 | case tslib.SyntaxKind.TrueKeyword: 322 | case tslib.SyntaxKind.FalseKeyword: 323 | return 'boolean'; 324 | case tslib.SyntaxKind.NullKeyword: 325 | case tslib.SyntaxKind.UndefinedKeyword: 326 | return null; 327 | case tslib.SyntaxKind.StringLiteral: 328 | return 'java.lang.String'; 329 | case tslib.SyntaxKind.Identifier: 330 | let writeRef = findLastWriteRef(source.fileName, node.getStart()); 331 | if (writeRef === undefined) 332 | return undefined; 333 | let typeName = writeRef.definition.name.split(':')[1]; 334 | if (typeName !== undefined) { 335 | typeName = typeName.trim(); 336 | switch (typeName) { 337 | case 'number': 338 | return 'int'; 339 | case 'boolean': 340 | return 'boolean'; 341 | case 'string': 342 | return 'java.lang.String'; 343 | default: 344 | break; 345 | } 346 | } 347 | let writeExpr = getNodeAtPosition(source, writeRef.reference.textSpan.start + 1).parent; 348 | while (writeExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 349 | writeExpr = writeExpr.parent; 350 | if (![ 351 | tslib.SyntaxKind.BinaryExpression, 352 | tslib.SyntaxKind.PropertyAssignment, 353 | tslib.SyntaxKind.VariableDeclaration, 354 | ].includes(writeExpr.kind)) 355 | return undefined; 356 | if (typeName !== undefined && typeName.indexOf('Java.Wrapper') === 0) 357 | return findInfoProviderForExpr(source, writeExpr.getChildAt(2)).getClassName(); 358 | return findTargetTypeForCommonType(source, writeExpr.getChildAt(2)); 359 | default: 360 | return undefined; 361 | } 362 | } 363 | function findLastWriteRef(fileName, position) { 364 | const refinfos = tsLS.findReferences(fileName, position); 365 | if (refinfos === undefined) 366 | return undefined; 367 | let reference = null; 368 | let definition = null; 369 | for (const refinfo of refinfos) { 370 | for (const ref of refinfo.references) { 371 | if (ref.isWriteAccess) { 372 | if (reference === null) { 373 | reference = ref; 374 | definition = refinfo.definition; 375 | continue; 376 | } 377 | if (ref.fileName === fileName) { 378 | if (reference.fileName !== ref.fileName) { 379 | reference = ref; 380 | definition = refinfo.definition; 381 | } 382 | else if (ref.textSpan.start > reference.textSpan.start) { 383 | reference = ref; 384 | definition = refinfo.definition; 385 | } 386 | } 387 | } 388 | } 389 | } 390 | return { reference, definition }; 391 | } 392 | function findFirstUseRef(fileName, position) { 393 | const refinfos = tsLS.findReferences(fileName, position); 394 | if (refinfos === undefined) 395 | return undefined; 396 | for (const refinfo of refinfos) { 397 | for (const ref of refinfo.references) { 398 | if (ref.isWriteAccess !== true) { 399 | return { reference: ref, definition: refinfo.definition }; 400 | } 401 | } 402 | } 403 | return undefined; 404 | } 405 | function findStringLiteral(source, node) { 406 | let current = node; 407 | while (true) { 408 | switch (current.kind) { 409 | case tslib.SyntaxKind.StringLiteral: 410 | return current.text; 411 | case tslib.SyntaxKind.Identifier: 412 | const writeRef = findLastWriteRef(source.fileName, current.getStart()); 413 | let writeExpr = getNodeAtPosition(source, writeRef.reference.textSpan.start + 1).parent; 414 | while (writeExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 415 | writeExpr = writeExpr.parent; 416 | if (![ 417 | tslib.SyntaxKind.BinaryExpression, 418 | tslib.SyntaxKind.PropertyAssignment, 419 | tslib.SyntaxKind.VariableDeclaration, 420 | ].includes(writeExpr.kind)) 421 | return undefined; 422 | current = writeExpr.getChildAt(2); 423 | break; 424 | default: 425 | return undefined; 426 | } 427 | } 428 | } 429 | (0, logger_1.log)("plugin loaded"); 430 | return proxy; 431 | } 432 | return { create }; 433 | } 434 | module.exports = init; 435 | -------------------------------------------------------------------------------- /dist/infoprovider.d.ts: -------------------------------------------------------------------------------- 1 | import * as tslib from "typescript/lib/tsserverlibrary"; 2 | export interface ClassInfoProvider { 3 | getClassName: () => string; 4 | getExtendClassNames: () => string[]; 5 | getPropInfoProvider: (name: string) => FieldInfoProvider | MethodInfoProvider; 6 | getFieldInfoProvider: (name: string) => FieldInfoProvider; 7 | getMethodInfoProvider: (name: string) => MethodInfoProvider; 8 | getCompletionDetail: (symbolName: string) => tslib.CompletionEntryDetails; 9 | getCompletionEntries: (originEntries?: tslib.CompletionEntry[]) => tslib.CompletionEntry[]; 10 | getDeclare: () => string; 11 | } 12 | export interface MethodInfoProvider { 13 | getClassName: () => string; 14 | getPropInfoProvider: (name: string) => ClassInfoProvider | MethodInfoProvider; 15 | hasOverload: () => boolean; 16 | getOverloadInfoProvider: (argTypes?: string[]) => MethodInfoProvider; 17 | getReturnClassName: (argTypes?: string[]) => string; 18 | getReturnInfoProvider: (argTypes?: string[]) => ClassInfoProvider; 19 | getParamClassNames: () => string[]; 20 | getCompletionDetail: (symbolName: string) => tslib.CompletionEntryDetails; 21 | getCompletionEntries: (originEntries?: tslib.CompletionEntry[]) => tslib.CompletionEntry[]; 22 | getDeclare: (argTypes?: string[]) => string; 23 | } 24 | export interface FieldInfoProvider { 25 | getClassName: () => string; 26 | getPropInfoProvider: (name: string) => ClassInfoProvider; 27 | getCompletionDetail: (symbolName: string) => tslib.CompletionEntryDetails; 28 | getCompletionEntries: (originEntries?: tslib.CompletionEntry[]) => tslib.CompletionEntry[]; 29 | getDeclare: () => string; 30 | } 31 | declare interface JavaMethodInfo { 32 | returnType: string; 33 | argumentTypes: string[]; 34 | } 35 | declare interface JavaClassInfo { 36 | alltypes: string[]; 37 | fields: { 38 | [index: string]: string; 39 | }; 40 | methods: { 41 | [index: string]: JavaMethodInfo[]; 42 | }; 43 | wrapperProps: string[]; 44 | } 45 | export declare class JavaProviderLoader { 46 | private config; 47 | classCache: Map; 48 | baseurl: string; 49 | constructor(config: any); 50 | getProviderByName(className: string): JavaClassInfoProvider; 51 | } 52 | export declare class ObjCProviderLoader { 53 | constructor(); 54 | } 55 | export declare class JavaClassInfoProvider implements ClassInfoProvider { 56 | classInfo: JavaClassInfo; 57 | methods: Map; 58 | fields: Map; 59 | cachedEntries: tslib.CompletionEntry[]; 60 | constructor(classInfo: JavaClassInfo); 61 | getClassName(): string; 62 | getExtendClassNames(): string[]; 63 | getPropInfoProvider(name: string): JavaMethodInfoProvider | JavaFieldInfoProvider; 64 | getFieldInfoProvider(fieldName: string): JavaFieldInfoProvider; 65 | getMethodInfoProvider(methodName: string): JavaMethodInfoProvider; 66 | getDeclare(): string; 67 | getCompletionDetail(name: string): tslib.CompletionEntryDetails; 68 | getCompletionEntries(originEntries?: tslib.CompletionEntry[]): tslib.CompletionEntry[]; 69 | } 70 | export declare class JavaMethodInfoProvider implements MethodInfoProvider { 71 | className: string; 72 | name: string; 73 | methodInfo: JavaMethodInfo[]; 74 | cachedEntries: tslib.CompletionEntry[]; 75 | constructor(className: string, name: string, methodInfo: JavaMethodInfo[]); 76 | getClassName(): string; 77 | hasOverload(): boolean; 78 | getPropInfoProvider(name: string): any; 79 | getMethodInfo(argTypes?: string[]): JavaMethodInfo; 80 | getOverloadInfoProvider(argTypes?: string[]): JavaMethodInfoProvider; 81 | getReturnClassName(argTypes?: string[]): string; 82 | getDeclare(argTypes?: string[]): string; 83 | getParamClassNames(): string[]; 84 | getReturnInfoProvider(argTypes?: string[]): JavaClassInfoProvider; 85 | getCompletionDetail(name: string): tslib.CompletionEntryDetails; 86 | getCompletionEntries(originEntries?: tslib.CompletionEntry[]): tslib.CompletionEntry[]; 87 | } 88 | export declare class JavaFieldInfoProvider implements FieldInfoProvider { 89 | className: string; 90 | name: string; 91 | type: string; 92 | cachedEntries: tslib.CompletionEntry[]; 93 | constructor(className: string, name: string, type: string); 94 | getDeclare(): string; 95 | getClassName(): string; 96 | getPropInfoProvider(name: string): JavaClassInfoProvider; 97 | getCompletionDetail(name: string): tslib.CompletionEntryDetails; 98 | getCompletionEntries(originEntries?: tslib.CompletionEntry[]): tslib.CompletionEntry[]; 99 | } 100 | export {}; 101 | -------------------------------------------------------------------------------- /dist/infoprovider.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.JavaFieldInfoProvider = exports.JavaMethodInfoProvider = exports.JavaClassInfoProvider = exports.ObjCProviderLoader = exports.JavaProviderLoader = void 0; 13 | const tslib = require("typescript/lib/tsserverlibrary"); 14 | const logger_1 = require("./logger"); 15 | const then_request_1 = require("then-request"); 16 | const sp = require("synchronized-promise"); 17 | let javaLoader; 18 | class JavaProviderLoader { 19 | constructor(config) { 20 | this.config = config; 21 | this.classCache = new Map(); 22 | this.baseurl = `http://${config.host || "127.0.0.1"}:${config.port || "28042"}`; 23 | javaLoader = this; 24 | } 25 | getProviderByName(className) { 26 | (0, logger_1.log)("getProviderByName", className); 27 | if (this.classCache[className] !== undefined) { 28 | return this.classCache[className]; 29 | } 30 | const doAsyncReq = () => __awaiter(this, void 0, void 0, function* () { 31 | const res = yield (0, then_request_1.default)("GET", this.baseurl + "/getJavaClassInfo?className=" + className, { 32 | timeout: 3000 33 | }); 34 | return res; 35 | }); 36 | const doSyncReq = sp(doAsyncReq); 37 | const res = doSyncReq(); 38 | (0, logger_1.log)(res.statusCode); 39 | if (res.statusCode !== 200) { 40 | this.classCache[className] = null; 41 | return null; 42 | } 43 | this.classCache[className] = new JavaClassInfoProvider(JSON.parse(res.getBody("utf-8"))); 44 | return this.classCache[className]; 45 | } 46 | } 47 | exports.JavaProviderLoader = JavaProviderLoader; 48 | let objCLoader; 49 | class ObjCProviderLoader { 50 | constructor() { 51 | } 52 | } 53 | exports.ObjCProviderLoader = ObjCProviderLoader; 54 | class JavaClassInfoProvider { 55 | constructor(classInfo) { 56 | this.classInfo = classInfo; 57 | this.methods = new Map(); 58 | this.fields = new Map(); 59 | this.cachedEntries = undefined; 60 | for (const name in classInfo.methods) { 61 | this.methods.set(name, new JavaMethodInfoProvider(classInfo.alltypes[0], name, classInfo.methods[name])); 62 | } 63 | this.methods.set("$init", this.methods.get("$new")); 64 | for (const name in classInfo.fields) { 65 | this.fields.set(name, new JavaFieldInfoProvider(classInfo.alltypes[0], name, classInfo.fields[name])); 66 | } 67 | } 68 | getClassName() { 69 | return this.classInfo.alltypes[0]; 70 | } 71 | getExtendClassNames() { 72 | return this.classInfo.alltypes; 73 | } 74 | getPropInfoProvider(name) { 75 | if (this.getMethodInfoProvider(name)) 76 | return this.getMethodInfoProvider(name); 77 | return this.getFieldInfoProvider(name); 78 | } 79 | getFieldInfoProvider(fieldName) { 80 | return this.fields.get(fieldName); 81 | } 82 | getMethodInfoProvider(methodName) { 83 | return this.methods.get(methodName); 84 | } 85 | getDeclare() { 86 | return "class " + this.classInfo.alltypes[0]; 87 | } 88 | getCompletionDetail(name) { 89 | const isMethod = this.methods.has(name); 90 | if (!isMethod && !this.getFieldInfoProvider(name)) 91 | return undefined; 92 | let details = { 93 | name: name, 94 | kind: isMethod ? tslib.ScriptElementKind.memberFunctionElement : 95 | tslib.ScriptElementKind.memberVariableElement, 96 | displayParts: [], 97 | documentation: [], 98 | kindModifiers: '' 99 | }; 100 | details.displayParts.push({ 101 | text: '(', 102 | kind: 'punctuation' 103 | }); 104 | details.displayParts.push({ 105 | text: isMethod ? 'method' : 'field', 106 | kind: 'text' 107 | }); 108 | details.displayParts.push({ 109 | text: ')', 110 | kind: 'punctuation' 111 | }); 112 | details.displayParts.push({ 113 | text: ' ', 114 | kind: 'space' 115 | }); 116 | details.displayParts.push({ 117 | text: this.getPropInfoProvider(name).getDeclare(), 118 | kind: 'text' 119 | }); 120 | return details; 121 | } 122 | getCompletionEntries(originEntries) { 123 | if (this.cachedEntries !== undefined) 124 | return this.cachedEntries; 125 | this.cachedEntries = []; 126 | for (const name of this.classInfo.wrapperProps) { 127 | let entry = { 128 | name: name, 129 | sortText: name, 130 | kind: tslib.ScriptElementKind.memberVariableElement, 131 | source: "" 132 | }; 133 | this.cachedEntries.push(entry); 134 | } 135 | this.methods.forEach((method, name) => { 136 | let entry = { 137 | name: name, 138 | sortText: name, 139 | kind: tslib.ScriptElementKind.memberFunctionElement, 140 | source: "Java_c:" + this.classInfo.alltypes[0] 141 | }; 142 | this.cachedEntries.push(entry); 143 | }); 144 | this.fields.forEach((field, name) => { 145 | let entry = { 146 | sortText: name, 147 | name: name, 148 | source: "Java_c:" + this.classInfo.alltypes[0], 149 | kind: tslib.ScriptElementKind.memberVariableElement 150 | }; 151 | this.cachedEntries.push(entry); 152 | }); 153 | return this.cachedEntries; 154 | } 155 | } 156 | exports.JavaClassInfoProvider = JavaClassInfoProvider; 157 | class JavaMethodInfoProvider { 158 | constructor(className, name, methodInfo) { 159 | this.className = className; 160 | this.name = name; 161 | this.methodInfo = methodInfo; 162 | this.cachedEntries = undefined; 163 | } 164 | getClassName() { 165 | return ''; 166 | } 167 | hasOverload() { 168 | return this.methodInfo.length > 1; 169 | } 170 | getPropInfoProvider(name) { 171 | return undefined; 172 | } 173 | getMethodInfo(argTypes) { 174 | if (argTypes === undefined) 175 | return this.methodInfo[0]; 176 | let midx = 0; 177 | for (const types of this.methodInfo) { 178 | if (argTypes.length === types.argumentTypes.length) { 179 | let hit = true; 180 | for (let i = 0; hit && i < types.argumentTypes.length; ++i) { 181 | if (argTypes[i] !== null && types.argumentTypes[i] !== argTypes[i]) { 182 | hit = false; 183 | let argClass = javaLoader.getProviderByName(argTypes[i]); 184 | for (const subType of argClass.getExtendClassNames()) { 185 | if (subType === types.argumentTypes[i]) { 186 | hit = true; 187 | break; 188 | } 189 | } 190 | } 191 | } 192 | if (hit) 193 | return this.methodInfo[midx]; 194 | } 195 | midx++; 196 | } 197 | return undefined; 198 | } 199 | getOverloadInfoProvider(argTypes) { 200 | const method = this.getMethodInfo(argTypes); 201 | if (method === undefined) 202 | return undefined; 203 | const aMethod = new JavaMethodInfoProvider(this.className, this.name, [method]); 204 | return aMethod; 205 | } 206 | getReturnClassName(argTypes) { 207 | const method = this.getMethodInfo(argTypes); 208 | if (method === undefined) 209 | return undefined; 210 | return method.returnType; 211 | } 212 | getDeclare(argTypes) { 213 | const method = this.getMethodInfo(argTypes); 214 | if (method === undefined) 215 | return undefined; 216 | if (method.argumentTypes.length === 0) 217 | return `${method.returnType} ${this.className}.${this.name}()`; 218 | else 219 | return `${method.returnType} ${this.className}.${this.name}('${method.argumentTypes.join("', '")}')`; 220 | } 221 | getParamClassNames() { 222 | return this.methodInfo[0].argumentTypes; 223 | } 224 | getReturnInfoProvider(argTypes) { 225 | let className = this.getReturnClassName(argTypes); 226 | return className ? javaLoader.getProviderByName(className) : undefined; 227 | } 228 | getCompletionDetail(name) { 229 | if (name.indexOf("overload(") !== 0) 230 | return undefined; 231 | let argTypes; 232 | if (name.length > 12) 233 | argTypes = name.slice(10, -2).split("', '"); 234 | if (name.length > 40) 235 | name = name.substring(0, 30) + "..."; 236 | let details = { 237 | name: name, 238 | kind: tslib.ScriptElementKind.memberFunctionElement, 239 | displayParts: [], 240 | documentation: [], 241 | kindModifiers: '' 242 | }; 243 | details.displayParts.push({ 244 | text: this.getDeclare(argTypes), 245 | kind: "text" 246 | }); 247 | details.documentation.push({ 248 | text: this.getDeclare(argTypes), 249 | kind: "text" 250 | }); 251 | return details; 252 | } 253 | getCompletionEntries(originEntries) { 254 | if (this.cachedEntries !== undefined) 255 | return this.cachedEntries; 256 | if (this.methodInfo.length === 0) 257 | return undefined; 258 | this.cachedEntries = []; 259 | const fridaMethodWarpperProps = [ 260 | "methodName", 261 | "holder", 262 | "type", 263 | "handle", 264 | "implementation", 265 | "returnType", 266 | "argumentTypes", 267 | "canInvokeWith", 268 | "clone", 269 | "invoke" 270 | ]; 271 | if (this.methodInfo.length > 1) { 272 | fridaMethodWarpperProps.push("overloads"); 273 | } 274 | fridaMethodWarpperProps.forEach(fieldName => { 275 | this.cachedEntries.push({ 276 | sortText: fieldName, 277 | name: fieldName, 278 | source: "Java_m:" + this.className + '.' + this.name, 279 | kind: tslib.ScriptElementKind.memberVariableElement 280 | }); 281 | }); 282 | if (this.methodInfo.length > 1) { 283 | this.methodInfo.forEach(info => { 284 | let overloadArg = "'" + info.argumentTypes.join("', '") + "'"; 285 | this.cachedEntries.push({ 286 | sortText: "overload(", 287 | name: "overload(" + overloadArg + ")", 288 | source: "Java_m:" + this.className + '.' + this.name, 289 | kind: tslib.ScriptElementKind.alias 290 | }); 291 | }); 292 | } 293 | return this.cachedEntries; 294 | } 295 | } 296 | exports.JavaMethodInfoProvider = JavaMethodInfoProvider; 297 | class JavaFieldInfoProvider { 298 | constructor(className, name, type) { 299 | this.className = className; 300 | this.name = name; 301 | this.type = type; 302 | this.cachedEntries = undefined; 303 | } 304 | getDeclare() { 305 | return `${this.type} ${this.className}.${this.name}`; 306 | } 307 | getClassName() { 308 | return this.type; 309 | } 310 | getPropInfoProvider(name) { 311 | if (name === 'value') { 312 | return javaLoader.getProviderByName(this.type); 313 | } 314 | if (name === 'holder') { 315 | return javaLoader.getProviderByName(this.className); 316 | } 317 | return undefined; 318 | } 319 | getCompletionDetail(name) { 320 | if (name !== 'value' && name !== 'holder') 321 | return undefined; 322 | let details = { 323 | name: name, 324 | kind: tslib.ScriptElementKind.memberFunctionElement, 325 | displayParts: [], 326 | documentation: [], 327 | kindModifiers: '' 328 | }; 329 | if (name === 'value') { 330 | details.displayParts.push({ 331 | text: this.getDeclare(), 332 | kind: 'text' 333 | }); 334 | } 335 | else if (name === 'holder') { 336 | details.displayParts.push({ 337 | text: javaLoader.getProviderByName(this.className).getDeclare(), 338 | kind: 'text' 339 | }); 340 | } 341 | return details; 342 | } 343 | getCompletionEntries(originEntries) { 344 | if (this.cachedEntries !== undefined) 345 | return this.cachedEntries; 346 | const fridaFieldWarpperProps = [ 347 | "value", 348 | "holder", 349 | "fieldType", 350 | "fieldReturnType" 351 | ]; 352 | this.cachedEntries = []; 353 | fridaFieldWarpperProps.forEach(propName => { 354 | this.cachedEntries.push({ 355 | sortText: propName, 356 | name: propName, 357 | source: "Java_f:" + this.className + '.' + this.name, 358 | kind: tslib.ScriptElementKind.memberVariableElement 359 | }); 360 | }); 361 | return this.cachedEntries; 362 | } 363 | } 364 | exports.JavaFieldInfoProvider = JavaFieldInfoProvider; 365 | -------------------------------------------------------------------------------- /dist/logger.d.ts: -------------------------------------------------------------------------------- 1 | export declare function log(...msg: { 2 | toString: () => string; 3 | }[]): void; 4 | export declare function setLogfile(filename: string): void; 5 | -------------------------------------------------------------------------------- /dist/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.setLogfile = exports.log = void 0; 4 | const fs = require("fs"); 5 | let logfile = null; 6 | function log(...msg) { 7 | if (logfile === null) 8 | return; 9 | fs.appendFileSync(logfile, msg.map(s => { 10 | if (s === undefined) 11 | return 'undefined'; 12 | if (s === null) 13 | return 'null'; 14 | return s.toString(); 15 | }).join(' ') + "\n"); 16 | } 17 | exports.log = log; 18 | function setLogfile(filename) { 19 | logfile = filename; 20 | fs.writeFileSync(logfile, ""); 21 | } 22 | exports.setLogfile = setLogfile; 23 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tacesrever/frida-tsplugin/d7a6382fb1fd800211cf8f454935155427c90b17/example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frida-tsplugin", 3 | "displayName": "frida-tsplugin", 4 | "description": "typescript autocomplete plugin for frida's java warpper", 5 | "version": "0.6.0", 6 | "publisher": "tacesrever", 7 | "repository": "", 8 | "main": "dist/index.js", 9 | "files": [ 10 | "dist", 11 | "agent" 12 | ], 13 | "scripts": { 14 | "compile": "tsc -p ." 15 | }, 16 | "dependencies": { 17 | "@types/node": "14.x", 18 | "node-abi": "^2.30.0", 19 | "synchronized-promise": "^0.3.1", 20 | "then-request": "^6.0.2", 21 | "typescript": "^4.9.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as tslib from 'typescript/lib/tsserverlibrary'; 3 | import {JavaProviderLoader} from './infoprovider'; 4 | import {ClassInfoProvider, MethodInfoProvider, FieldInfoProvider} from './infoprovider'; 5 | import {setLogfile, log} from './logger'; 6 | 7 | function init(mod: { typescript: typeof tslib }) { 8 | const typescript = mod.typescript; 9 | 10 | function create(info: tslib.server.PluginCreateInfo) { 11 | const tsLS = info.languageService; 12 | if(info.config.logfile !== undefined) setLogfile(info.config.logfile); 13 | const proxy: tslib.LanguageService = Object.create(null); 14 | for (let k of Object.keys(info.languageService) as Array) { 15 | const x = info.languageService[k]; 16 | proxy[k] = (...args: Array<{}>) => x.apply(info.languageService, args); 17 | } 18 | 19 | const javaLoader = new JavaProviderLoader(info.config); 20 | 21 | proxy.getCompletionsAtPosition = (fileName: string, position: number, options: tslib.GetCompletionsAtPositionOptions) => { 22 | const source = getSourceFile(fileName); 23 | let oret = tsLS.getCompletionsAtPosition(fileName, position, options); 24 | try { 25 | const completeFor = getNodeAtPosition(source, position).parent.getChildAt(0); 26 | log("trigger:", options.triggerCharacter, 'completeFor ' + completeFor.getText()); 27 | const provider = findInfoProviderForExpr(source, completeFor); 28 | if(provider === undefined) return oret; 29 | const entries = provider.getCompletionEntries(oret ? oret.entries : undefined); 30 | if(entries === undefined) return oret; 31 | if(oret === undefined) { 32 | oret = { 33 | entries: entries, 34 | isGlobalCompletion: false, 35 | isMemberCompletion: true, 36 | isNewIdentifierLocation: false 37 | } 38 | } else { 39 | oret.entries = entries; 40 | } 41 | } catch(e) { 42 | log(e.stack); 43 | } 44 | return oret; 45 | } 46 | proxy.getCompletionEntryDetails = (fileName, position, name, options, source, pref, data) => { 47 | log("getCompletionEntryDetails", name, source); 48 | if(source && source.indexOf("Java_") === 0) { 49 | let [type, className] = source.substr(5).split(':'); 50 | if(type === 'c') { 51 | const provider = javaLoader.getProviderByName(className); 52 | const details = provider.getCompletionDetail(name); 53 | if(details !== undefined) return details; 54 | } else { 55 | const divpos = className.lastIndexOf('.'); 56 | const propName = className.substr(divpos + 1); 57 | className = className.substr(0, divpos); 58 | 59 | const provider = javaLoader.getProviderByName(className); 60 | const propProvider = provider.getPropInfoProvider(propName); 61 | const details = propProvider.getCompletionDetail(name); 62 | if(details !== undefined) return details; 63 | } 64 | } 65 | return tsLS.getCompletionEntryDetails(fileName, position, name, options, source, pref, data); 66 | } 67 | proxy.getQuickInfoAtPosition = (fileName, position) => { 68 | const info = tsLS.getQuickInfoAtPosition(fileName, position); 69 | const source = getSourceFile(fileName); 70 | try { 71 | let getInfoFor = getNodeAtPosition(source, position); 72 | if(getInfoFor.parent.kind === tslib.SyntaxKind.PropertyAccessExpression 73 | && getInfoFor.parent.getChildAt(0) !== getInfoFor) { 74 | getInfoFor = getInfoFor.parent; 75 | } 76 | const provider = findInfoProviderForExpr(source, getInfoFor); 77 | if(provider === undefined) return info; 78 | // get info for overload function 79 | if(getInfoFor.parent.kind === tslib.SyntaxKind.CallExpression 80 | && getInfoFor.parent.getChildAt(0) === getInfoFor) { 81 | const callExpr = getInfoFor.parent as tslib.CallExpression; 82 | const argTypes = findArgTypesForCallExpr(source, callExpr); 83 | if(argTypes === undefined) return info; 84 | info.displayParts = [{ 85 | text: (provider as MethodInfoProvider).getDeclare(argTypes), 86 | kind: 'text' 87 | }] 88 | return info; 89 | } 90 | info.displayParts = [{ 91 | text: provider.getDeclare(), 92 | kind: 'text' 93 | }] 94 | } catch(e) { log(e); } 95 | return info; 96 | } 97 | 98 | function getSourceFile(fileName: string) { 99 | return (tsLS as any).getNonBoundSourceFile(fileName) as tslib.SourceFile; 100 | } 101 | 102 | function getNodeAtPosition(source: tslib.SourceFile, position: number) { 103 | let current: tslib.Node = source; 104 | outer: while (true) { 105 | for (const child of current.getChildren(source)) { 106 | const start = child.getFullStart(); 107 | if (start > position) { 108 | return current; 109 | } 110 | 111 | const end = child.getEnd(); 112 | if (position <= end) { 113 | current = child; 114 | continue outer; 115 | } 116 | } 117 | return current; 118 | } 119 | } 120 | 121 | function findInfoProviderForExpr(source: tslib.SourceFile, node: tslib.Node) 122 | : ClassInfoProvider | FieldInfoProvider | MethodInfoProvider { 123 | let current = node; 124 | log("findInfoProviderForExpr", node.getText(), node.kind); 125 | while (true) { 126 | switch(current.kind) { 127 | case tslib.SyntaxKind.CallExpression: 128 | return findReturnInfoProviderForCallExpr(source, current as tslib.CallExpression); 129 | 130 | case tslib.SyntaxKind.Identifier: 131 | let writeRef = findLastWriteRef(source.fileName, current.getStart()); 132 | let typeName = writeRef.definition.name.split(':')[1]; 133 | if(typeName !== undefined) { 134 | typeName = typeName.trim(); 135 | if(typeName !== 'any' && typeName.indexOf('Java.Wrapper') !== 0) { 136 | log("type missmatch:", typeName); 137 | return undefined; 138 | } 139 | } 140 | let writeExpr = getNodeAtPosition(source, writeRef.reference.textSpan.start + 1).parent; 141 | if(writeRef.definition.kind === tslib.ScriptElementKind.parameterElement) { 142 | current = writeExpr; 143 | break; 144 | } 145 | while(writeExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 146 | writeExpr = writeExpr.parent; 147 | if(! [ 148 | tslib.SyntaxKind.BinaryExpression, 149 | tslib.SyntaxKind.PropertyAssignment, 150 | tslib.SyntaxKind.VariableDeclaration, 151 | ].includes(writeExpr.kind)) { 152 | return undefined; 153 | } 154 | 155 | current = writeExpr.getChildAt(2); 156 | break; 157 | case tslib.SyntaxKind.ElementAccessExpression: 158 | case tslib.SyntaxKind.PropertyAccessExpression: 159 | let parentNode = current.getChildAt(0); 160 | let propNode = current.getChildAt(2); 161 | let propWriteRef = undefined; 162 | let propName = propNode.getText(); 163 | if(!["value", "$new", "$init", "overload"].includes(propName)) 164 | propWriteRef = findLastWriteRef(source.fileName, propNode.getEnd()); 165 | if(propWriteRef === undefined) { 166 | let provider = findInfoProviderForExpr(source, parentNode); 167 | if(provider === undefined) { 168 | return undefined; 169 | } 170 | return provider.getPropInfoProvider(propNode.getText()); 171 | } 172 | let tmpExpr = getNodeAtPosition(source, propWriteRef.reference.textSpan.start).parent; 173 | if(tmpExpr.kind === tslib.SyntaxKind.PropertyAssignment) { 174 | current = tmpExpr.getChildAt(2); 175 | break; 176 | } 177 | while(tmpExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 178 | tmpExpr = tmpExpr.parent; 179 | if(tmpExpr.kind === tslib.SyntaxKind.BinaryExpression) { 180 | current = tmpExpr.getChildAt(2); 181 | break; 182 | } 183 | return undefined; 184 | // parse this and params from: 185 | // func.impl = (...args) 186 | // TODO: Java.choose => onMatch: function (instance) 187 | case tslib.SyntaxKind.ThisKeyword: 188 | case tslib.SyntaxKind.Parameter: 189 | let target = current; 190 | while(![tslib.SyntaxKind.FunctionExpression, 191 | tslib.SyntaxKind.FunctionDeclaration, 192 | tslib.SyntaxKind.ArrowFunction] 193 | .includes(current.kind)) 194 | current = current.parent; 195 | let funcDefExpr = current as tslib.FunctionLikeDeclaration; 196 | let funcAssignExpr: tslib.Node; 197 | if(funcDefExpr.name !== undefined) { 198 | // TODO: for named function, find use of it 199 | return undefined; 200 | } else { 201 | funcAssignExpr = funcDefExpr.parent; 202 | } 203 | if(! [ 204 | tslib.SyntaxKind.BinaryExpression, 205 | tslib.SyntaxKind.PropertyAssignment, 206 | ].includes(funcAssignExpr.kind)) 207 | return undefined; 208 | const leftValue = funcAssignExpr.getChildAt(0); 209 | if(leftValue.kind === tslib.SyntaxKind.PropertyAccessExpression 210 | && leftValue.getChildAt(2).getText() === 'implementation') { 211 | let methodNode = leftValue.getChildAt(0); 212 | if(target.kind === tslib.SyntaxKind.ThisKeyword) { 213 | if(methodNode.kind === tslib.SyntaxKind.CallExpression && 214 | methodNode.getChildAt(0).getChildAt(2).getText() === 'overload') { 215 | methodNode = methodNode.getChildAt(0).getChildAt(0); 216 | } 217 | let classNode = methodNode.getChildAt(0); 218 | return findInfoProviderForExpr(source, classNode); 219 | } 220 | // is parameter 221 | let i; 222 | for(i = 0; i < funcDefExpr.parameters.length; ++i) { 223 | if(funcDefExpr.parameters[i] === target) { 224 | break; 225 | } 226 | } 227 | if(methodNode.kind === tslib.SyntaxKind.CallExpression 228 | && methodNode.getChildAt(0).getChildAt(2).getText() === 'overload') { 229 | const argTypeNameNode = (methodNode as tslib.CallExpression).arguments[i]; 230 | const argTypeName = findStringLiteral(source, argTypeNameNode); 231 | if(argTypeName === undefined) return undefined; 232 | return javaLoader.getProviderByName(argTypeName); 233 | } 234 | const method = findInfoProviderForExpr(source, methodNode) as MethodInfoProvider; 235 | if(method === undefined) return undefined; 236 | return javaLoader.getProviderByName(method.getParamClassNames()[i]); 237 | } 238 | default: 239 | return undefined; 240 | } 241 | } 242 | } 243 | 244 | function findReturnInfoProviderForCallExpr(source: tslib.SourceFile, callExpr: tslib.CallExpression) { 245 | let funcExpr = callExpr.expression; 246 | if(funcExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) { 247 | let funcPropAccExpr = funcExpr as tslib.PropertyAccessExpression; 248 | let funcName = funcPropAccExpr.name.text; 249 | 250 | if(funcPropAccExpr.expression.getText() === "Java") { 251 | if(funcName === 'use') { 252 | return javaLoader.getProviderByName(findStringLiteral(source, callExpr.arguments[0])); 253 | } 254 | if(funcName === 'cast') { 255 | let classNode = callExpr.arguments[1]; 256 | if(classNode === undefined) return undefined; 257 | return findInfoProviderForExpr(source, classNode); 258 | } 259 | return undefined; 260 | } 261 | let parent = findInfoProviderForExpr(source, funcPropAccExpr.expression); 262 | if(parent === undefined) return undefined; 263 | if(funcName === 'call') { 264 | let method = parent as MethodInfoProvider; 265 | let argTypes = findArgTypesForCallExpr(source, callExpr); 266 | if(argTypes === undefined) return undefined; 267 | argTypes = argTypes.slice(1); 268 | return method.getOverloadInfoProvider(argTypes).getReturnInfoProvider(); 269 | } else if (funcName === 'apply') { 270 | let method = parent as MethodInfoProvider; 271 | // TODO: detact apply argTypes 272 | return method.getReturnInfoProvider(); 273 | } else if(funcName === 'overload') { 274 | let method = parent as MethodInfoProvider; 275 | let argTypes = callExpr.arguments.map(expr => findStringLiteral(source, expr)); 276 | if(argTypes === undefined) return undefined; 277 | return method.getOverloadInfoProvider(argTypes); 278 | } else { 279 | let klass = parent as ClassInfoProvider; 280 | let method = klass.getMethodInfoProvider(funcName); 281 | if(!method.hasOverload()) 282 | return method.getReturnInfoProvider(); 283 | let argTypes = findArgTypesForCallExpr(source, callExpr); 284 | if(argTypes === undefined) return undefined; 285 | return method.getReturnInfoProvider(argTypes); 286 | } 287 | } 288 | let method = findInfoProviderForExpr(source, funcExpr) as MethodInfoProvider; 289 | if(method === undefined) return undefined; 290 | return method.getReturnInfoProvider(); 291 | } 292 | 293 | function findArgTypesForCallExpr(source: tslib.SourceFile, callExpr: tslib.CallExpression) { 294 | let argTypes: string[] = []; 295 | for(let i = 0; i < callExpr.arguments.length; ++i) { 296 | let arg = callExpr.arguments[i]; 297 | let typeName = findTargetTypeForCommonType(source, arg); 298 | if(typeName === undefined) { 299 | let type = findInfoProviderForExpr(source, arg); 300 | if(type === undefined) return undefined; 301 | typeName = type.getClassName(); 302 | } 303 | argTypes.push(typeName); 304 | } 305 | return argTypes; 306 | } 307 | 308 | function findTargetTypeForCommonType(source: tslib.SourceFile, node: tslib.Node): string { 309 | switch(node.kind) { 310 | case tslib.SyntaxKind.NumericLiteral: 311 | return 'int'; 312 | case tslib.SyntaxKind.TrueKeyword: 313 | case tslib.SyntaxKind.FalseKeyword: 314 | return 'boolean'; 315 | case tslib.SyntaxKind.NullKeyword: 316 | case tslib.SyntaxKind.UndefinedKeyword: 317 | return null; 318 | case tslib.SyntaxKind.StringLiteral: 319 | return 'java.lang.String'; 320 | case tslib.SyntaxKind.Identifier: 321 | let writeRef = findLastWriteRef(source.fileName, node.getStart()); 322 | if(writeRef === undefined) return undefined; 323 | let typeName = writeRef.definition.name.split(':')[1]; 324 | if(typeName !== undefined) { 325 | typeName = typeName.trim(); 326 | switch(typeName) { 327 | case 'number': 328 | return 'int'; 329 | case 'boolean': 330 | return 'boolean'; 331 | case 'string': 332 | return 'java.lang.String'; 333 | default: 334 | break; 335 | } 336 | } 337 | let writeExpr = getNodeAtPosition(source, writeRef.reference.textSpan.start + 1).parent; 338 | while(writeExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 339 | writeExpr = writeExpr.parent; 340 | if(! [ 341 | tslib.SyntaxKind.BinaryExpression, 342 | tslib.SyntaxKind.PropertyAssignment, 343 | tslib.SyntaxKind.VariableDeclaration, 344 | ].includes(writeExpr.kind)) 345 | return undefined; 346 | if(typeName !== undefined && typeName.indexOf('Java.Wrapper') === 0) 347 | return findInfoProviderForExpr(source, writeExpr.getChildAt(2)).getClassName(); 348 | return findTargetTypeForCommonType(source, writeExpr.getChildAt(2)); 349 | default: 350 | return undefined; 351 | } 352 | } 353 | 354 | function findLastWriteRef(fileName: string, position: number) { 355 | const refinfos = tsLS.findReferences(fileName, position); 356 | if(refinfos === undefined) return undefined; 357 | let reference : tslib.ReferenceEntry = null; 358 | let definition : tslib.ReferencedSymbolDefinitionInfo = null; 359 | 360 | for (const refinfo of refinfos) { 361 | for (const ref of refinfo.references) { 362 | if(ref.isWriteAccess) { 363 | if(reference === null) { 364 | reference = ref; 365 | definition = refinfo.definition; 366 | continue; 367 | } 368 | 369 | if(ref.fileName === fileName) { 370 | if(reference.fileName !== ref.fileName) { 371 | reference = ref; 372 | definition = refinfo.definition; 373 | } else if(ref.textSpan.start > reference.textSpan.start) { 374 | reference = ref; 375 | definition = refinfo.definition; 376 | } 377 | } 378 | } 379 | } 380 | } 381 | return {reference, definition}; 382 | } 383 | 384 | function findFirstUseRef(fileName: string, position: number) { 385 | const refinfos = tsLS.findReferences(fileName, position); 386 | if(refinfos === undefined) return undefined; 387 | 388 | for (const refinfo of refinfos) { 389 | for (const ref of refinfo.references) { 390 | if(ref.isWriteAccess !== true) { 391 | return {reference: ref, definition: refinfo.definition}; 392 | } 393 | } 394 | } 395 | 396 | return undefined; 397 | } 398 | 399 | function findStringLiteral(source: tslib.SourceFile, node: tslib.Node) { 400 | let current = node; 401 | while (true) { 402 | switch(current.kind) { 403 | case tslib.SyntaxKind.StringLiteral: 404 | return (current as tslib.StringLiteral).text; 405 | case tslib.SyntaxKind.Identifier: 406 | const writeRef = findLastWriteRef(source.fileName, current.getStart()); 407 | let writeExpr = getNodeAtPosition(source, writeRef.reference.textSpan.start + 1).parent; 408 | while(writeExpr.kind === tslib.SyntaxKind.PropertyAccessExpression) 409 | writeExpr = writeExpr.parent; 410 | if(! [ 411 | tslib.SyntaxKind.BinaryExpression, 412 | tslib.SyntaxKind.PropertyAssignment, 413 | tslib.SyntaxKind.VariableDeclaration, 414 | ].includes(writeExpr.kind)) 415 | return undefined; 416 | current = writeExpr.getChildAt(2); 417 | break; 418 | default: 419 | return undefined; 420 | } 421 | } 422 | } 423 | 424 | log("plugin loaded"); 425 | return proxy; 426 | } 427 | 428 | return { create }; 429 | } 430 | 431 | export = init; -------------------------------------------------------------------------------- /src/infoprovider.ts: -------------------------------------------------------------------------------- 1 | import * as tslib from "typescript/lib/tsserverlibrary"; 2 | import {log} from './logger'; 3 | import request from 'then-request'; 4 | import * as sp from 'synchronized-promise'; 5 | 6 | export interface ClassInfoProvider { 7 | getClassName: () => string; 8 | getExtendClassNames: () => string[]; 9 | getPropInfoProvider: (name: string) => FieldInfoProvider | MethodInfoProvider; 10 | getFieldInfoProvider: (name: string) => FieldInfoProvider; 11 | getMethodInfoProvider: (name: string) => MethodInfoProvider; 12 | getCompletionDetail: (symbolName: string) => tslib.CompletionEntryDetails; 13 | getCompletionEntries: (originEntries?: tslib.CompletionEntry[]) => tslib.CompletionEntry[]; 14 | getDeclare: () => string; 15 | } 16 | 17 | export interface MethodInfoProvider { 18 | getClassName: () => string; 19 | getPropInfoProvider: (name: string) => ClassInfoProvider | MethodInfoProvider; 20 | hasOverload: () => boolean; 21 | getOverloadInfoProvider: (argTypes?: string[]) => MethodInfoProvider; 22 | getReturnClassName: (argTypes?: string[]) => string; 23 | getReturnInfoProvider: (argTypes?: string[]) => ClassInfoProvider; 24 | getParamClassNames: () => string[]; 25 | getCompletionDetail: (symbolName: string) => tslib.CompletionEntryDetails; 26 | getCompletionEntries: (originEntries?: tslib.CompletionEntry[]) => tslib.CompletionEntry[]; 27 | getDeclare: (argTypes?: string[]) => string; 28 | } 29 | 30 | export interface FieldInfoProvider { 31 | getClassName: () => string; 32 | getPropInfoProvider: (name: string) => ClassInfoProvider; 33 | getCompletionDetail: (symbolName: string) => tslib.CompletionEntryDetails; 34 | getCompletionEntries: (originEntries?: tslib.CompletionEntry[]) => tslib.CompletionEntry[]; 35 | getDeclare: () => string; 36 | } 37 | 38 | declare interface JavaMethodInfo { 39 | returnType: string 40 | argumentTypes: string[] 41 | } 42 | declare interface JavaClassInfo { 43 | alltypes: string[] 44 | fields: { 45 | [index: string]: string 46 | } 47 | methods: { 48 | [index: string]: JavaMethodInfo[] 49 | } 50 | wrapperProps: string[] 51 | } 52 | let javaLoader: JavaProviderLoader; 53 | export class JavaProviderLoader { 54 | classCache: Map = new Map(); 55 | baseurl: string 56 | constructor(private config: any) { 57 | this.baseurl = `http://${config.host || "127.0.0.1"}:${config.port || "28042"}`; 58 | javaLoader = this; 59 | } 60 | 61 | getProviderByName(className: string): JavaClassInfoProvider { 62 | log("getProviderByName", className); 63 | if(this.classCache[className] !== undefined) { 64 | return this.classCache[className]; 65 | } 66 | const doAsyncReq = async () => { 67 | const res = await request("GET", this.baseurl + "/getJavaClassInfo?className=" + className, { 68 | timeout: 3000 69 | }); 70 | return res 71 | } 72 | const doSyncReq = (sp as any)(doAsyncReq); 73 | const res = doSyncReq(); 74 | log(res.statusCode); 75 | if(res.statusCode !== 200) { 76 | this.classCache[className] = null; 77 | return null; 78 | } 79 | this.classCache[className] = new JavaClassInfoProvider(JSON.parse(res.getBody("utf-8"))); 80 | return this.classCache[className]; 81 | 82 | } 83 | } 84 | 85 | let objCLoader: ObjCProviderLoader; 86 | export class ObjCProviderLoader { 87 | constructor() { 88 | 89 | } 90 | } 91 | 92 | export class JavaClassInfoProvider implements ClassInfoProvider { 93 | methods: Map = new Map(); 94 | fields: Map = new Map(); 95 | cachedEntries: tslib.CompletionEntry[] = undefined; 96 | constructor(public classInfo: JavaClassInfo) { 97 | for(const name in classInfo.methods) { 98 | this.methods.set(name, new JavaMethodInfoProvider(classInfo.alltypes[0], name, classInfo.methods[name])); 99 | } 100 | this.methods.set("$init", this.methods.get("$new")); 101 | for(const name in classInfo.fields) { 102 | this.fields.set(name, new JavaFieldInfoProvider(classInfo.alltypes[0], name, classInfo.fields[name])); 103 | } 104 | } 105 | 106 | getClassName() { 107 | return this.classInfo.alltypes[0]; 108 | } 109 | 110 | getExtendClassNames() { 111 | return this.classInfo.alltypes; 112 | } 113 | 114 | getPropInfoProvider(name: string) { 115 | if(this.getMethodInfoProvider(name)) return this.getMethodInfoProvider(name); 116 | return this.getFieldInfoProvider(name); 117 | } 118 | 119 | getFieldInfoProvider(fieldName: string) { 120 | return this.fields.get(fieldName); 121 | } 122 | 123 | getMethodInfoProvider(methodName: string) { 124 | return this.methods.get(methodName); 125 | } 126 | 127 | getDeclare() { 128 | return "class " + this.classInfo.alltypes[0]; 129 | } 130 | 131 | getCompletionDetail(name: string) { 132 | const isMethod = this.methods.has(name); 133 | if(!isMethod && !this.getFieldInfoProvider(name)) return undefined; 134 | let details: tslib.CompletionEntryDetails = { 135 | name: name, 136 | kind: isMethod? tslib.ScriptElementKind.memberFunctionElement: 137 | tslib.ScriptElementKind.memberVariableElement, 138 | displayParts: [], 139 | documentation: [], 140 | kindModifiers: '' 141 | } 142 | details.displayParts.push({ 143 | text: '(', 144 | kind: 'punctuation' 145 | }); 146 | details.displayParts.push({ 147 | text: isMethod? 'method': 'field', 148 | kind: 'text' 149 | }); 150 | details.displayParts.push({ 151 | text: ')', 152 | kind: 'punctuation' 153 | }); 154 | details.displayParts.push({ 155 | text: ' ', 156 | kind: 'space' 157 | }); 158 | details.displayParts.push({ 159 | text: this.getPropInfoProvider(name).getDeclare(), 160 | kind: 'text' 161 | }); 162 | return details; 163 | } 164 | 165 | getCompletionEntries(originEntries?: tslib.CompletionEntry[]) { 166 | if(this.cachedEntries !== undefined) return this.cachedEntries; 167 | this.cachedEntries = []; 168 | for(const name of this.classInfo.wrapperProps) { 169 | let entry: tslib.CompletionEntry = { 170 | name: name, 171 | sortText: name, 172 | kind: tslib.ScriptElementKind.memberVariableElement, 173 | source: "" 174 | }; 175 | this.cachedEntries.push(entry); 176 | } 177 | 178 | this.methods.forEach((method, name) => { 179 | let entry: tslib.CompletionEntry = { 180 | name: name, 181 | sortText: name, 182 | kind: tslib.ScriptElementKind.memberFunctionElement, 183 | source: "Java_c:" + this.classInfo.alltypes[0] 184 | }; 185 | this.cachedEntries.push(entry); 186 | }); 187 | 188 | this.fields.forEach((field, name) => { 189 | let entry: tslib.CompletionEntry = { 190 | sortText: name, 191 | name: name, 192 | source: "Java_c:" + this.classInfo.alltypes[0], 193 | kind: tslib.ScriptElementKind.memberVariableElement 194 | }; 195 | this.cachedEntries.push(entry); 196 | }); 197 | return this.cachedEntries; 198 | } 199 | } 200 | 201 | export class JavaMethodInfoProvider implements MethodInfoProvider { 202 | cachedEntries: tslib.CompletionEntry[] = undefined; 203 | constructor(public className: string, public name: string, public methodInfo: JavaMethodInfo[]) { 204 | } 205 | 206 | getClassName() { 207 | return ''; 208 | } 209 | 210 | hasOverload() { 211 | return this.methodInfo.length > 1; 212 | } 213 | 214 | getPropInfoProvider(name: string) { 215 | return undefined; 216 | } 217 | 218 | getMethodInfo(argTypes?: string[]) { 219 | if(argTypes === undefined) 220 | return this.methodInfo[0]; 221 | let midx = 0; 222 | for(const types of this.methodInfo) { 223 | if(argTypes.length === types.argumentTypes.length) { 224 | let hit = true; 225 | for(let i = 0; hit && i < types.argumentTypes.length; ++i) { 226 | if(argTypes[i] !== null && types.argumentTypes[i] !== argTypes[i]) { 227 | hit = false; 228 | let argClass = javaLoader.getProviderByName(argTypes[i]); 229 | for(const subType of argClass.getExtendClassNames()) { 230 | if(subType === types.argumentTypes[i]) { 231 | hit = true; 232 | break; 233 | } 234 | } 235 | } 236 | } 237 | if(hit) return this.methodInfo[midx]; 238 | } 239 | midx++; 240 | } 241 | return undefined; 242 | } 243 | 244 | getOverloadInfoProvider(argTypes?: string[]) { 245 | const method = this.getMethodInfo(argTypes); 246 | if(method === undefined) return undefined; 247 | const aMethod = new JavaMethodInfoProvider(this.className, this.name, [method]); 248 | return aMethod; 249 | } 250 | 251 | getReturnClassName(argTypes?: string[]): string { 252 | const method = this.getMethodInfo(argTypes); 253 | if(method === undefined) return undefined; 254 | return method.returnType; 255 | } 256 | 257 | getDeclare(argTypes?: string[]) { 258 | const method = this.getMethodInfo(argTypes); 259 | if(method === undefined) return undefined; 260 | if(method.argumentTypes.length === 0) return `${method.returnType} ${this.className}.${this.name}()`; 261 | else return `${method.returnType} ${this.className}.${this.name}('${method.argumentTypes.join("', '")}')`; 262 | } 263 | 264 | getParamClassNames() { 265 | return this.methodInfo[0].argumentTypes; 266 | } 267 | 268 | getReturnInfoProvider(argTypes?: string[]) { 269 | let className = this.getReturnClassName(argTypes); 270 | return className? javaLoader.getProviderByName(className): undefined; 271 | } 272 | 273 | getCompletionDetail(name: string) { 274 | if(name.indexOf("overload(") !== 0) return undefined; 275 | let argTypes: string[]; 276 | if(name.length > 12) 277 | argTypes = name.slice(10, -2).split("', '"); 278 | if(name.length > 40) name = name.substring(0, 30) + "..."; 279 | let details: tslib.CompletionEntryDetails = { 280 | name: name, 281 | kind: tslib.ScriptElementKind.memberFunctionElement, 282 | displayParts: [], 283 | documentation: [], 284 | kindModifiers: '' 285 | } 286 | details.displayParts.push({ 287 | text: this.getDeclare(argTypes), 288 | kind: "text" 289 | }); 290 | details.documentation.push({ 291 | text: this.getDeclare(argTypes), 292 | kind: "text" 293 | }); 294 | return details; 295 | } 296 | 297 | getCompletionEntries(originEntries?: tslib.CompletionEntry[]) { 298 | if(this.cachedEntries !== undefined) return this.cachedEntries; 299 | if(this.methodInfo.length === 0) return undefined; 300 | this.cachedEntries = []; 301 | const fridaMethodWarpperProps = [ 302 | "methodName", 303 | "holder", 304 | "type", 305 | "handle", 306 | "implementation", 307 | "returnType", 308 | "argumentTypes", 309 | "canInvokeWith", 310 | "clone", 311 | "invoke" 312 | ] 313 | if(this.methodInfo.length > 1) { 314 | fridaMethodWarpperProps.push("overloads"); 315 | } 316 | fridaMethodWarpperProps.forEach(fieldName => { 317 | this.cachedEntries.push({ 318 | sortText: fieldName, 319 | name: fieldName, 320 | source: "Java_m:" + this.className + '.' + this.name, 321 | kind: tslib.ScriptElementKind.memberVariableElement 322 | }); 323 | }); 324 | if(this.methodInfo.length > 1) { 325 | this.methodInfo.forEach(info => { 326 | let overloadArg = "'" + info.argumentTypes.join("', '") + "'"; 327 | 328 | this.cachedEntries.push({ 329 | sortText: "overload(", 330 | name: "overload(" + overloadArg + ")", 331 | source: "Java_m:" + this.className + '.' + this.name, 332 | kind: tslib.ScriptElementKind.alias 333 | }); 334 | }); 335 | } 336 | return this.cachedEntries; 337 | } 338 | } 339 | 340 | export class JavaFieldInfoProvider implements FieldInfoProvider { 341 | cachedEntries: tslib.CompletionEntry[] = undefined; 342 | constructor(public className: string, public name: string, public type: string) { 343 | } 344 | 345 | getDeclare() { 346 | return `${this.type} ${this.className}.${this.name}`; 347 | } 348 | 349 | getClassName() { 350 | return this.type; 351 | } 352 | 353 | getPropInfoProvider(name: string) { 354 | if(name === 'value') { 355 | return javaLoader.getProviderByName(this.type); 356 | } 357 | if(name === 'holder') { 358 | return javaLoader.getProviderByName(this.className); 359 | } 360 | return undefined; 361 | } 362 | 363 | getCompletionDetail(name: string) { 364 | if(name !== 'value' && name !== 'holder') return undefined; 365 | let details: tslib.CompletionEntryDetails = { 366 | name: name, 367 | kind: tslib.ScriptElementKind.memberFunctionElement, 368 | displayParts: [], 369 | documentation: [], 370 | kindModifiers: '' 371 | } 372 | if(name === 'value') { 373 | details.displayParts.push({ 374 | text: this.getDeclare(), 375 | kind: 'text' 376 | }); 377 | } 378 | else if(name === 'holder') { 379 | details.displayParts.push({ 380 | text: javaLoader.getProviderByName(this.className).getDeclare(), 381 | kind: 'text' 382 | }); 383 | } 384 | return details; 385 | } 386 | 387 | getCompletionEntries(originEntries?: tslib.CompletionEntry[]) { 388 | if(this.cachedEntries !== undefined) return this.cachedEntries; 389 | const fridaFieldWarpperProps = [ 390 | "value", 391 | "holder", 392 | "fieldType", 393 | "fieldReturnType" 394 | ]; 395 | this.cachedEntries = []; 396 | fridaFieldWarpperProps.forEach(propName => { 397 | this.cachedEntries.push({ 398 | sortText: propName, 399 | name: propName, 400 | source: "Java_f:" + this.className + '.' + this.name, 401 | kind: tslib.ScriptElementKind.memberVariableElement 402 | }); 403 | }) 404 | return this.cachedEntries; 405 | } 406 | } -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | let logfile: string = null; 4 | export function log(...msg: {toString: () => string}[]) { 5 | if(logfile === null) return; 6 | fs.appendFileSync(logfile, msg.map(s => { 7 | if(s === undefined) return 'undefined'; 8 | if(s === null) return 'null'; 9 | return s.toString(); 10 | }).join(' ') + "\n"); 11 | } 12 | 13 | export function setLogfile(filename: string) { 14 | logfile = filename; 15 | fs.writeFileSync(logfile, ""); 16 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2016", 5 | "outDir": "dist", 6 | "rootDir": "src", 7 | "allowJs": true, 8 | "declaration": true 9 | }, 10 | "exclude": [ 11 | "dist", 12 | "node_modules", 13 | "agent" 14 | ] 15 | } --------------------------------------------------------------------------------