initial test of context-dependant code completion

This commit is contained in:
Dave Holoway
2020-06-23 17:03:40 +01:00
parent cd6bf29e9f
commit 591907f523
5 changed files with 108 additions and 35 deletions

View File

@@ -205,7 +205,7 @@ function parse(source, typemap) {
const timeEnd = name => (timers.delete(name), console.timeEnd(name)); const timeEnd = name => (timers.delete(name), console.timeEnd(name));
try { try {
time('tokenize'); time('tokenize');
tokens = new TokenList(tokenize(source)); tokens = new TokenList(unit.tokens = tokenize(source));
problems = tokens.problems; problems = tokens.problems;
timeEnd('tokenize'); timeEnd('tokenize');
@@ -318,16 +318,22 @@ function parseUnit(tokens, unit, typemap) {
*/ */
function packageDeclaration(tokens) { function packageDeclaration(tokens) {
tokens.mark(); tokens.mark();
const package_token = tokens.current;
package_token.loc = 'pkgname:';
tokens.expectValue('package'); tokens.expectValue('package');
const pkg_name_parts = []; let pkg_name_parts = [], dot;
for (;;) { for (;;) {
let name = tokens.current; let name = tokens.current;
if (!tokens.isKind('ident')) { if (!tokens.isKind('ident')) {
name = null; name = null;
addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`)); addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`));
} }
if (name) pkg_name_parts.push(name.value); if (name) {
if (tokens.isValue('.')) { name.loc = `pkgname:${pkg_name_parts.join('/')}`;
pkg_name_parts.push(name.value);
}
if (dot = tokens.getIfValue('.')) {
dot.loc = `pkgname:${pkg_name_parts.join('/')}`;
continue; continue;
} }
const decl_tokens = tokens.markEnd(); const decl_tokens = tokens.markEnd();

View File

@@ -366,6 +366,7 @@ class SourceConstructor extends Constructor {
this.sourceParameters = parameters; this.sourceParameters = parameters;
this.throws = throws; this.throws = throws;
this.body = body; this.body = body;
this.parsed = null;
} }
get hasImplementation() { get hasImplementation() {
@@ -411,6 +412,7 @@ class SourceMethod extends Method {
this.sourceParameters = parameters; this.sourceParameters = parameters;
this.throws = throws; this.throws = throws;
this.body = body; this.body = body;
this.parsed = null;
} }
get hasImplementation() { get hasImplementation() {
@@ -449,6 +451,7 @@ class SourceInitialiser extends MethodBase {
this.owner = owner; this.owner = owner;
this.modifierTokens = modifiers; this.modifierTokens = modifiers;
this.body = body; this.body = body;
this.parsed = null;
} }
/** /**
@@ -535,12 +538,38 @@ class SourceImport {
} }
class SourceUnit { class SourceUnit {
/** @type {Token[]} */
tokens = [];
/** @type {SourcePackage} */ /** @type {SourcePackage} */
package_ = null; package_ = null;
/** @type {SourceImport[]} */ /** @type {SourceImport[]} */
imports = []; imports = [];
/** @type {SourceType[]} */ /** @type {SourceType[]} */
types = []; types = [];
/**
*
* @param {number} char_index
*/
getCompletionOptionsAt(char_index) {
let i = 0;
let loc = '';
for (let tok of this.tokens) {
if (char_index > tok.range.start + tok.range.length) {
i++;
continue;
}
while (i > 0 && tok.kind === 'wsc') {
tok = this.tokens[--i];
}
loc = tok.loc;
break;
}
return {
index: char_index,
loc: loc,
};
}
} }
class SourceArrayType extends ArrayType { class SourceArrayType extends ArrayType {

View File

@@ -49,6 +49,7 @@ class Token extends TextBlock {
constructor(text, start, length, kind) { constructor(text, start, length, kind) {
super(new BlockRange(text, start, length), tokenKindToSimplified(text, start, length, kind)); super(new BlockRange(text, start, length), tokenKindToSimplified(text, start, length, kind));
this.kind = kind; this.kind = kind;
this.loc = '';
} }
get value() { get value() {

View File

@@ -3,6 +3,28 @@ const { resolveImports } = require('../java/import-resolver');
const { SourceUnit } = require('./source-types'); const { SourceUnit } = require('./source-types');
const { parseBody } = require('./body-parser3'); const { parseBody } = require('./body-parser3');
/**
* @param {SourceUnit} unit
* @param {Map<string, CEIType>} androidLibrary
*/
function parseMethodBodies(unit, androidLibrary) {
const resolved_types = [
...resolveImports(androidLibrary, [], [], null).resolved,
...unit.imports.filter(i => i.resolved).map(i => i.resolved),
]
unit.types.forEach(t => {
t.initers.forEach(i => {
i.parsed = parseBody(i, resolved_types, androidLibrary);
})
t.constructors.forEach(c => {
c.parsed = parseBody(c, resolved_types, androidLibrary);
})
t.sourceMethods.forEach(m => {
m.parsed = parseBody(m, resolved_types, androidLibrary);
})
})
}
/** /**
* @param {SourceUnit} unit * @param {SourceUnit} unit
* @param {Map<string, CEIType>} androidLibrary * @param {Map<string, CEIType>} androidLibrary
@@ -12,41 +34,20 @@ function validate(unit, androidLibrary) {
console.time('validation'); console.time('validation');
let probs = []; let probs = [];
const resolved_types = [
...resolveImports(androidLibrary, [], [], null).resolved,
...unit.imports.filter(i => i.resolved).map(i => i.resolved),
]
unit.types.forEach(t => {
t.initers.forEach(i => {
const parsed = parseBody(i, resolved_types, androidLibrary);
if (parsed)
probs = probs.concat(parsed.problems)
})
t.constructors.forEach(c => {
const parsed = parseBody(c, resolved_types, androidLibrary);
if (parsed)
probs = probs.concat(parsed.problems)
})
t.sourceMethods.forEach(m => {
const parsed = parseBody(m, resolved_types, androidLibrary);
if (parsed)
probs = probs.concat(parsed.problems)
})
})
const module_validaters = [ const module_validaters = [
// require('./validation/multiple-package-decls'), // require('./validation/multiple-package-decls'),
// require('./validation/unit-decl-order'), // require('./validation/unit-decl-order'),
// require('./validation/duplicate-members'), // require('./validation/duplicate-members'),
// require('./validation/parse-errors'), // require('./validation/parse-errors'),
require('./validation/modifier-errors'), // require('./validation/modifier-errors'),
require('./validation/unresolved-imports'), // require('./validation/unresolved-imports'),
require('./validation/invalid-types'), // require('./validation/invalid-types'),
require('./validation/bad-extends'), // require('./validation/bad-extends'),
require('./validation/bad-implements'), // require('./validation/bad-implements'),
require('./validation/non-implemented-interfaces'), // require('./validation/non-implemented-interfaces'),
require('./validation/bad-overrides'), // require('./validation/bad-overrides'),
require('./validation/missing-constructor'), // require('./validation/missing-constructor'),
//require('./validation/expression-compatibility'), //require('./validation/expression-compatibility'),
]; ];
let problems = [ let problems = [
@@ -73,4 +74,5 @@ function validate(unit, androidLibrary) {
module.exports = { module.exports = {
validate, validate,
parseMethodBodies,
} }

View File

@@ -22,7 +22,7 @@ const { loadAndroidLibrary, CEIType } = require('java-mti');
const { ParseProblem } = require('./java/parser'); 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 } = require('./java/validater'); const { validate, parseMethodBodies } = require('./java/validater');
/** /**
* @typedef {Map<string, CEIType>} AndroidLibrary * @typedef {Map<string, CEIType>} AndroidLibrary
@@ -52,6 +52,9 @@ function reparse(uri, content) {
} }
const typemap = new Map(androidLibrary); const typemap = new Map(androidLibrary);
const result = parse(content, typemap); const result = parse(content, typemap);
if (result) {
parseMethodBodies(result.unit, typemap);
}
parsed = { parsed = {
content, content,
uri, uri,
@@ -261,7 +264,7 @@ async function validateTextDocument(textDocument) {
if (parsed && parsed.result) { if (parsed && parsed.result) {
try { try {
problems = [...parsed.result.problems, ...validate(parsed.result.unit, parsed.typemap)]; //problems = [...parsed.result.problems, ...validate(parsed.result.unit, parsed.typemap)];
} catch(err) { } catch(err) {
console.error(err); console.error(err);
} }
@@ -355,6 +358,38 @@ connection.onCompletion(
} }
const lib = (parsed && parsed.typemap) || androidLibrary; const lib = (parsed && parsed.typemap) || androidLibrary;
if (!lib) return []; if (!lib) return [];
if (parsed.result && parsed.result.unit) {
const index = parsed.indexAt(_textDocumentPosition.position);
const options = parsed.result.unit.getCompletionOptionsAt(index);
console.log(options);
if (/^pkgname:/.test(options.loc)) {
const pkg = options.loc.split(':').pop();
let pkgs;
if (pkg === '') {
// root packages
pkgs = [...parsed.typemap.keys()].reduce((set,typename) => {
const m = typename.match(/(.+?)\//);
m && set.add(m[1]);
return set;
}, new Set());
} else {
// sub-package
const search_pkg = pkg + '/';
pkgs = [...parsed.typemap.keys()].reduce((arr,typename) => {
if (typename.startsWith(search_pkg)) {
const m = typename.slice(search_pkg.length).match(/^(.+?)\//);
if (m) arr.add(m[1]);
}
return arr;
}, new Set());
}
return [...pkgs].filter(x => x).sort().map(pkg => ({
label: pkg,
kind: CompletionItemKind.Unit,
data: -1,
}));
}
}
const typeKindMap = { const typeKindMap = {
class: CompletionItemKind.Class, class: CompletionItemKind.Class,
interface: CompletionItemKind.Interface, interface: CompletionItemKind.Interface,