mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 09:59:25 +00:00
support for member expressions
This commit is contained in:
@@ -1703,6 +1703,7 @@ function qualifiers(matches, tokens, mdecls, scope, imports, typemap) {
|
|||||||
*/
|
*/
|
||||||
function memberQualifier(matches, tokens, mdecls, scope, imports, typemap) {
|
function memberQualifier(matches, tokens, mdecls, scope, imports, typemap) {
|
||||||
tokens.mark();
|
tokens.mark();
|
||||||
|
const dot = tokens.current;
|
||||||
tokens.expectValue('.');
|
tokens.expectValue('.');
|
||||||
let expr, label = `${matches.source}.${tokens.current.value}`;
|
let expr, label = `${matches.source}.${tokens.current.value}`;
|
||||||
let types = [], package_name = '';
|
let types = [], package_name = '';
|
||||||
@@ -1724,7 +1725,7 @@ function memberQualifier(matches, tokens, mdecls, scope, imports, typemap) {
|
|||||||
member = null;
|
member = null;
|
||||||
addproblem(tokens, ParseProblem.Error(tokens.current, `Identifier expected`));
|
addproblem(tokens, ParseProblem.Error(tokens.current, `Identifier expected`));
|
||||||
}
|
}
|
||||||
expr = new MemberExpression(matches, member);
|
expr = new MemberExpression(matches, dot, member);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
tokens.inc();
|
tokens.inc();
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ const ParseProblem = require('../parsetypes/parse-problem');
|
|||||||
class MemberExpression extends Expression {
|
class MemberExpression extends Expression {
|
||||||
/**
|
/**
|
||||||
* @param {ResolvedIdent} instance
|
* @param {ResolvedIdent} instance
|
||||||
|
* @param {Token} dot
|
||||||
* @param {Token|null} member
|
* @param {Token|null} member
|
||||||
*/
|
*/
|
||||||
constructor(instance, member) {
|
constructor(instance, dot, member) {
|
||||||
super();
|
super();
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
|
this.dot = dot;
|
||||||
// member will be null for incomplete expressions
|
// member will be null for incomplete expressions
|
||||||
this.member = member;
|
this.member = member;
|
||||||
}
|
}
|
||||||
@@ -27,16 +29,18 @@ class MemberExpression extends Expression {
|
|||||||
*/
|
*/
|
||||||
resolveExpression(ri) {
|
resolveExpression(ri) {
|
||||||
let instance = this.instance.resolveExpression(ri);
|
let instance = this.instance.resolveExpression(ri);
|
||||||
if (instance instanceof TypeIdentType) {
|
|
||||||
// static member
|
|
||||||
instance = instance.type;
|
|
||||||
}
|
|
||||||
if (instance instanceof AnyType) {
|
if (instance instanceof AnyType) {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
const ident = this.member.value;
|
|
||||||
|
|
||||||
if (instance instanceof PackageNameType) {
|
if (instance instanceof PackageNameType) {
|
||||||
|
this.dot.loc = `fqs:${instance.package_name}`;
|
||||||
|
if (!this.member) {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
this.member.loc = this.dot.loc;
|
||||||
|
const ident = this.member.value;
|
||||||
const { sub_package_name, type } = resolveNextPackage(instance.package_name, ident, ri.typemap);
|
const { sub_package_name, type } = resolveNextPackage(instance.package_name, ident, ri.typemap);
|
||||||
if (!type && !sub_package_name) {
|
if (!type && !sub_package_name) {
|
||||||
ri.problems.push(ParseProblem.Error(this.member, `Unresolved identifier: '${ident}'`));
|
ri.problems.push(ParseProblem.Error(this.member, `Unresolved identifier: '${ident}'`));
|
||||||
@@ -46,10 +50,24 @@ class MemberExpression extends Expression {
|
|||||||
: AnyType.Instance;
|
: AnyType.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let loc = `fqi`;
|
||||||
|
if (instance instanceof TypeIdentType) {
|
||||||
|
loc = 'fqs';
|
||||||
|
instance = instance.type;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(instance instanceof JavaType)) {
|
if (!(instance instanceof JavaType)) {
|
||||||
ri.problems.push(ParseProblem.Error(this.member, `Unresolved member: '${ident}'`));
|
|
||||||
return AnyType.Instance;
|
return AnyType.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dot.loc = `${loc}:${instance.fullyDottedTypeName}`
|
||||||
|
if (!this.member) {
|
||||||
|
ri.problems.push(ParseProblem.Error(this.dot, `Identifier expected`));
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.member.loc = this.dot.loc;
|
||||||
|
const ident = this.member.value;
|
||||||
const field = instance.fields.find(f => f.name === ident);
|
const field = instance.fields.find(f => f.name === ident);
|
||||||
if (field) {
|
if (field) {
|
||||||
return field.type;
|
return field.type;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const { ParseProblem } = require('./java/parser');
|
|||||||
const { parse } = require('./java/body-parser3');
|
const { parse } = require('./java/body-parser3');
|
||||||
const { SourceUnit } = require('./java/source-types');
|
const { SourceUnit } = require('./java/source-types');
|
||||||
const { validate, parseMethodBodies } = require('./java/validater');
|
const { validate, parseMethodBodies } = require('./java/validater');
|
||||||
|
const { getTypeInheritanceList } = require('./java/expression-resolver');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Map<string, CEIType>} AndroidLibrary
|
* @typedef {Map<string, CEIType>} AndroidLibrary
|
||||||
@@ -343,24 +344,28 @@ connection.onDidChangeWatchedFiles((_change) => {
|
|||||||
connection.console.log('We received a file change event');
|
connection.console.log('We received a file change event');
|
||||||
});
|
});
|
||||||
|
|
||||||
function getFullyQualifiedNameCompletion(name) {
|
/**
|
||||||
if (name === '') {
|
* @param {string} dotted_name
|
||||||
|
* @param {{ statics: boolean }} opts
|
||||||
|
*/
|
||||||
|
function getFullyQualifiedNameCompletion(dotted_name, opts) {
|
||||||
|
if (dotted_name === '') {
|
||||||
return getPackageCompletion('');
|
return getPackageCompletion('');
|
||||||
}
|
}
|
||||||
// name is a fully dotted name, possibly including a static member
|
// name is a fully dotted name, possibly including members and their fields
|
||||||
let typelist = [...parsed.typemap.keys()];
|
let typelist = [...parsed.typemap.keys()];
|
||||||
|
|
||||||
const split_name = name.split('.');
|
const split_name = dotted_name.split('.');
|
||||||
let pkgname = '';
|
let pkgname = '';
|
||||||
/** @type {JavaType} */
|
/** @type {JavaType} */
|
||||||
let type = null, typename = '';
|
let type = null, typename = '';
|
||||||
for (let name_part of split_name) {
|
for (let name_part of split_name) {
|
||||||
if (type) {
|
if (type) {
|
||||||
if (typelist.includes(`${typename}$${name_part}`)) {
|
if (opts.statics && typelist.includes(`${typename}$${name_part}`)) {
|
||||||
type = parsed.typemap.get(typename = `${typename}$${name_part}`);
|
type = parsed.typemap.get(typename = `${typename}$${name_part}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return [];
|
break;
|
||||||
}
|
}
|
||||||
typename = pkgname + name_part;
|
typename = pkgname + name_part;
|
||||||
if (typelist.includes(typename)) {
|
if (typelist.includes(typename)) {
|
||||||
@@ -371,9 +376,36 @@ function getFullyQualifiedNameCompletion(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type) {
|
if (type) {
|
||||||
// add inner types and static fields
|
if (!(type instanceof CEIType)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// add inner types, fields and methods
|
||||||
|
class FirstSetMap extends Map {
|
||||||
|
set(key, value) {
|
||||||
|
return this.has(key) ? this : super.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fields = new FirstSetMap(), methods = new FirstSetMap();
|
||||||
|
/**
|
||||||
|
* @param {string[]} modifiers
|
||||||
|
* @param {JavaType} t
|
||||||
|
*/
|
||||||
|
function shouldInclude(modifiers, t) {
|
||||||
|
if (opts.statics !== modifiers.includes('static')) return;
|
||||||
|
if (modifiers.includes('public')) return true;
|
||||||
|
if (modifiers.includes('protected')) return true;
|
||||||
|
if (modifiers.includes('private') && t === type) return true;
|
||||||
|
// @ts-ignore
|
||||||
|
return t.packageName === type.packageName;
|
||||||
|
}
|
||||||
|
function sortByName(a,b) { return a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}) }
|
||||||
|
getTypeInheritanceList(type).forEach((t,idx) => {
|
||||||
|
t.fields.sort(sortByName).filter(f => shouldInclude(f.modifiers, t)).forEach(f => fields.set(f.name, {f, sortText: `${idx+100}${f.name}`}));
|
||||||
|
t.methods.sort(sortByName).filter(f => shouldInclude(f.modifiers, t)).forEach(m => methods.set(m.methodSignature, {m, sortText: `${idx+100}${m.name}`}));
|
||||||
|
});
|
||||||
return [
|
return [
|
||||||
...typelist.map(t => {
|
...typelist.map(t => {
|
||||||
|
if (!opts.statics) return;
|
||||||
if (!t.startsWith(typename)) return;
|
if (!t.startsWith(typename)) return;
|
||||||
const m = t.slice(typename.length).match(/^\$.+/);
|
const m = t.slice(typename.length).match(/^\$.+/);
|
||||||
if (!m) return;
|
if (!m) return;
|
||||||
@@ -383,9 +415,20 @@ function getFullyQualifiedNameCompletion(name) {
|
|||||||
data: -1,
|
data: -1,
|
||||||
}
|
}
|
||||||
}).filter(x => x),
|
}).filter(x => x),
|
||||||
...type.fields.filter(m => m.modifiers.includes('static')).map(f => ({
|
// fields
|
||||||
label: f.name,
|
...[...fields.values()].map(f => ({
|
||||||
|
label: `${f.f.name}: ${f.f.type.simpleTypeName}`,
|
||||||
|
insertText: f.f.name,
|
||||||
kind: CompletionItemKind.Field,
|
kind: CompletionItemKind.Field,
|
||||||
|
sortText: f.sortText,
|
||||||
|
data: -1,
|
||||||
|
})),
|
||||||
|
// methods
|
||||||
|
...[...methods.values()].map(m => ({
|
||||||
|
label: m.m.shortlabel,
|
||||||
|
kind: CompletionItemKind.Method,
|
||||||
|
insertText: m.m.name,
|
||||||
|
sortText: m.sortText,
|
||||||
data: -1,
|
data: -1,
|
||||||
}))
|
}))
|
||||||
]
|
]
|
||||||
@@ -420,26 +463,33 @@ function getFullyQualifiedNameCompletion(name) {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPackageCompletion(pkg) {
|
function getRootPackageCompletions() {
|
||||||
let pkgs;
|
const pkgs = [...parsed.typemap.keys()].reduce((set,typename) => {
|
||||||
if (pkg === '') {
|
|
||||||
// root packages
|
|
||||||
pkgs = [...parsed.typemap.keys()].reduce((set,typename) => {
|
|
||||||
const m = typename.match(/(.+?)\//);
|
const m = typename.match(/(.+?)\//);
|
||||||
m && set.add(m[1]);
|
m && set.add(m[1]);
|
||||||
return set;
|
return set;
|
||||||
}, new Set());
|
}, new Set());
|
||||||
} else {
|
return [...pkgs].filter(x => x).sort().map(pkg => ({
|
||||||
|
label: pkg,
|
||||||
|
kind: CompletionItemKind.Unit,
|
||||||
|
data: -1,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackageCompletion(pkg) {
|
||||||
|
if (pkg === '') {
|
||||||
|
return getRootPackageCompletions();
|
||||||
|
}
|
||||||
// sub-package
|
// sub-package
|
||||||
const search_pkg = pkg + '/';
|
const search_pkg = pkg + '/';
|
||||||
pkgs = [...parsed.typemap.keys()].reduce((arr,typename) => {
|
const pkgs = [...parsed.typemap.keys()].reduce((arr,typename) => {
|
||||||
if (typename.startsWith(search_pkg)) {
|
if (typename.startsWith(search_pkg)) {
|
||||||
const m = typename.slice(search_pkg.length).match(/^(.+?)\//);
|
const m = typename.slice(search_pkg.length).match(/^(.+?)\//);
|
||||||
if (m) arr.add(m[1]);
|
if (m) arr.add(m[1]);
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}, new Set());
|
}, new Set());
|
||||||
}
|
|
||||||
return [...pkgs].filter(x => x).sort().map(pkg => ({
|
return [...pkgs].filter(x => x).sort().map(pkg => ({
|
||||||
label: pkg,
|
label: pkg,
|
||||||
kind: CompletionItemKind.Unit,
|
kind: CompletionItemKind.Unit,
|
||||||
@@ -470,7 +520,16 @@ connection.onCompletion(
|
|||||||
return getPackageCompletion(options.loc.split(':').pop());
|
return getPackageCompletion(options.loc.split(':').pop());
|
||||||
}
|
}
|
||||||
if (/^fqn:/.test(options.loc)) {
|
if (/^fqn:/.test(options.loc)) {
|
||||||
return getFullyQualifiedNameCompletion(options.loc.split(':').pop());
|
// fully-qualified type/field name
|
||||||
|
return getFullyQualifiedNameCompletion(options.loc.split(':').pop(), { statics: true });
|
||||||
|
}
|
||||||
|
if (/^fqs:/.test(options.loc)) {
|
||||||
|
// fully-qualified expression
|
||||||
|
return getFullyQualifiedNameCompletion(options.loc.split(':').pop(), { statics: true });
|
||||||
|
}
|
||||||
|
if (/^fqi:/.test(options.loc)) {
|
||||||
|
// fully-qualified expression
|
||||||
|
return getFullyQualifiedNameCompletion(options.loc.split(':').pop(), { statics: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const typeKindMap = {
|
const typeKindMap = {
|
||||||
@@ -506,6 +565,7 @@ connection.onCompletion(
|
|||||||
data: t.shortSignature,
|
data: t.shortSignature,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
...getRootPackageCompletions()
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user