mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
updated validation to use new JavaTypes module instead of MTIs
This commit is contained in:
@@ -12,9 +12,9 @@ function fetchImportedTypes(typenames, dotted_import, demandload) {
|
||||
const matcher = demandload
|
||||
// for demand-load, we search for any types that begin with the specified import name
|
||||
// - note that after the import text, only words and $ are allowed (because additional dots would imply a subpackage)
|
||||
? new RegExp(`^${dotted_import.replace(/\./g, '[.$]')}[.$][\\w$]+$`, 'gm')
|
||||
? new RegExp(`^${dotted_import.replace(/\./g, '[/$]')}[/$][\\w$]+$`, 'gm')
|
||||
// for exact-load, we search for any types that precisely matches the specified import name
|
||||
: new RegExp(`^${dotted_import.replace(/\./g, '[.$]')}$`, 'gm');
|
||||
: new RegExp(`^${dotted_import.replace(/\./g, '[/$]')}$`, 'gm');
|
||||
|
||||
// run the regex against the list of type names
|
||||
const matching_names = typenames.match(matcher);
|
||||
@@ -37,23 +37,20 @@ function resolveImportTypes(typenames, import_decl) {
|
||||
* - followed by import declarations (in order of declaration),
|
||||
* - followed by implicit packages
|
||||
*
|
||||
* @param {*} androidLibrary imported types from the Android platform library
|
||||
* @param {Map<string, import('java-mti').JavaType>} androidLibrary
|
||||
* @param {import('java-mti').JavaType[]} sourceTypes
|
||||
* @param {ImportBlock[]} imports list of declared imports in the module
|
||||
* @param {string} package_name package name of the module
|
||||
* @param {import('./mti').Type[]} source_mtis MTIs representing types declared in the source
|
||||
* @param {string[]} [implicitPackages] list of implicit demand-load packages
|
||||
*/
|
||||
function resolveImports(androidLibrary, imports, package_name, source_mtis, implicitPackages = ['java.lang']) {
|
||||
/**
|
||||
* create a new Map that maps JRE type names to MTI instances
|
||||
* @type {Map<string, import('./mti').Type>}
|
||||
*/
|
||||
const typemap = new Map(
|
||||
androidLibrary.types.map(mti => [`${mti.package}.${mti.name}`, mti])
|
||||
);
|
||||
// add the source MTIs
|
||||
// todo - should we overwrite entries when source MTIs match types in the library?
|
||||
source_mtis.forEach(mti => typemap.set(`${mti.package}.${mti.name}`, mti))
|
||||
function resolveImports(androidLibrary, sourceTypes, imports, package_name, implicitPackages = ['java.lang']) {
|
||||
|
||||
const typemap = new Map(androidLibrary);
|
||||
|
||||
sourceTypes.forEach(t => {
|
||||
// todo - should we overwrite entries when source types match types in the library?
|
||||
typemap.set(t.shortSignature, t);
|
||||
})
|
||||
|
||||
// construct the list of typenames
|
||||
const typenames = [...typemap.keys()].join('\n');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -329,6 +329,7 @@ const markers = {
|
||||
annotation: 'Q',
|
||||
brackets: 'R',
|
||||
typeArgs: 'T',
|
||||
enumvalues: 'U',
|
||||
varDecl: 'V',
|
||||
typeDecl: 'Z',
|
||||
error: ' ',
|
||||
@@ -342,9 +343,10 @@ const markers = {
|
||||
* @param {string} [marker]
|
||||
* @param {boolean} [recursive]
|
||||
* @param {{}} [parseClass]
|
||||
* @param {{time:boolean}} [opts]
|
||||
*/
|
||||
function group(sourceblocks, id, re, marker, recursive, parseClass) {
|
||||
console.time(id);
|
||||
function group(sourceblocks, id, re, marker, recursive, parseClass, opts) {
|
||||
if (opts && opts.time) console.time(id);
|
||||
let grouped = [];
|
||||
let sourcemap = sourceblocks.sourcemap();
|
||||
if (!re.global) {
|
||||
@@ -356,7 +358,9 @@ function group(sourceblocks, id, re, marker, recursive, parseClass) {
|
||||
for (let m; m = re.exec(sourcemap.simplified); ) {
|
||||
// every group must start and end on a definite boundary
|
||||
const start = sourcemap.map[m.index];
|
||||
const end = sourcemap.map[m.index + m[0].length -1];
|
||||
let end = sourcemap.map[m.index + m[0].length - 1];
|
||||
if (end === undefined)
|
||||
end = sourcemap.map[m.index + m[0].length];
|
||||
if (start === undefined || end === undefined) {
|
||||
throw new Error('undefined group boundary')
|
||||
}
|
||||
@@ -380,7 +384,7 @@ function group(sourceblocks, id, re, marker, recursive, parseClass) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
console.timeEnd(id);
|
||||
if (opts && opts.time) console.timeEnd(id);
|
||||
return grouped;
|
||||
}
|
||||
|
||||
@@ -439,6 +443,20 @@ class DeclaredVariableBlock extends DeclarationBlock {
|
||||
|
||||
class FieldBlock extends DeclaredVariableBlock { }
|
||||
|
||||
class EnumValueBlock extends TextBlock {
|
||||
|
||||
static parseRE = /(?<=^\{\s*)[W](\s*=[^,;]*)?(\s*,\s*[W](\s*=[^,;]*)?)*(\s*;)?/g
|
||||
|
||||
/**
|
||||
* @param {TextBlockArray} section
|
||||
* @param {string} simplified
|
||||
*/
|
||||
constructor(section, simplified) {
|
||||
super(section, simplified);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ParameterBlock extends DeclaredVariableBlock {
|
||||
static parseRE = /([MQ](\s*[MQ])*\s+)?(V)/g
|
||||
}
|
||||
@@ -476,19 +494,19 @@ class MCBlock extends DeclarationBlock {
|
||||
this.parsed.parameters = group(param_block, 'param', ParameterBlock.parseRE, markers.parameter, false, ParameterBlock);
|
||||
// parameters must be a comma-separated list
|
||||
const sm = param_block.sourcemap();
|
||||
if (sm.simplified.search(/^\(( *F( *, *F)*)? *\)/) === 0) {
|
||||
return;
|
||||
if (sm.simplified.search(/^\((\s*F(\s*,\s*F)*)?\s*\)/) === 0) {
|
||||
return this.parsed.parameters;
|
||||
}
|
||||
let invalid = sm.simplified.match(/^(\( *)(F?)(?: *, *F)* */);
|
||||
let invalid = sm.simplified.match(/^(\(\s*)(F?)(?:\s*,\s*F)*\s*/);
|
||||
if (!invalid) {
|
||||
// should never happen, but ignore
|
||||
return;
|
||||
return this.parsed.parameters;
|
||||
}
|
||||
const token_idx = invalid[2]
|
||||
? sm.map[invalid[0].length] // there's a problem with a subsequent declaration
|
||||
: sm.map[invalid[1].length] // there's a problem with the first declaration
|
||||
const token = param_block.blocks[token_idx];
|
||||
if (!token) return;
|
||||
if (!token) return this.parsed.parameters;
|
||||
this.parsed.errors = [token];
|
||||
}
|
||||
return this.parsed.parameters;
|
||||
@@ -594,17 +612,21 @@ class TypeDeclBlock extends DeclarationBlock {
|
||||
static marker = 'Z';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {TextBlockArray} blocks
|
||||
* @param {string} simplified
|
||||
*/
|
||||
constructor(blocks, simplified) {
|
||||
super(blocks, simplified);
|
||||
this.decl = blocks;
|
||||
this.kindToken = this.decl.blocks.find(b => !/^[MQ\s]/.test(b.simplified));
|
||||
this.name_token = this.decl.blocks.find(b => b.simplified.startsWith('W'));
|
||||
this.typevars_token = this.decl.blocks.find(b => b.simplified.startsWith('T'));
|
||||
this.extends_token = this.decl.blocks.find(b => b.simplified.startsWith('J'));
|
||||
this.implements_token = this.decl.blocks.find(b => b.simplified.startsWith('K'));
|
||||
this.extends_decl = this.decl.blocks.find(b => b.simplified.startsWith('J'));
|
||||
this.implements_decl = this.decl.blocks.find(b => b.simplified.startsWith('K'));
|
||||
/** @type {TypeDeclBlock} */
|
||||
this.outer_type = null;
|
||||
/** @type {ModuleBlock} */
|
||||
this.mod = null;
|
||||
this.parsed = {
|
||||
/** @type {{name: string, decl:(TextBlock|BoundedTypeVar)}[]} */
|
||||
typevars: null,
|
||||
@@ -627,13 +649,24 @@ class TypeDeclBlock extends DeclarationBlock {
|
||||
* Return the kind of type declared
|
||||
*/
|
||||
kind() {
|
||||
const kindToken = this.decl.blocks.find(b => !/^[MQ\s]/.test(b.simplified));
|
||||
/** @type {'class'|'enum'|'interface'|'@'} */
|
||||
// @ts-ignore
|
||||
const id = kindToken.toSource();
|
||||
const id = this.kindToken.toSource();
|
||||
return id === '@' ? '@interface' : id;
|
||||
}
|
||||
|
||||
get fullyDottedName() {
|
||||
return this.shortSignature.replace(/[/$]/g, '.');
|
||||
}
|
||||
|
||||
get shortSignature() {
|
||||
if (this.outer_type) {
|
||||
return `${this.outer_type.shortSignature}$${this.simpleName}`
|
||||
}
|
||||
const pkg = this.mod.packageName.replace(/\./g, '/');
|
||||
return `${pkg}${pkg ? '/' : ''}${this.simpleName}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type name with no type-parameter info
|
||||
*/
|
||||
@@ -714,9 +747,13 @@ class TypeDeclBlock extends DeclarationBlock {
|
||||
parseAnnotations(body);
|
||||
parseEITDecls(body);
|
||||
/** @type {TypeDeclBlock[]} */
|
||||
this.parsed.types = parseTypeDecls(body);
|
||||
this.parsed.types = parseTypeDecls(body, this, this.mod);
|
||||
|
||||
group(body, 'var-decl', VarDeclBlock.parseRE, markers.varDecl, false, VarDeclBlock);
|
||||
if (this.kind() === 'enum') {
|
||||
/** @type {EnumValueBlock[]} */
|
||||
this.parsed.enums = group(body, 'enumvalue', EnumValueBlock.parseRE, markers.enumvalues, false, EnumValueBlock);
|
||||
}
|
||||
/** @type {FieldBlock[]} */
|
||||
this.parsed.fields = group(body, 'field', FieldBlock.parseRE, markers.field, false, FieldBlock);
|
||||
/** @type {MethodBlock[]} */
|
||||
@@ -725,9 +762,9 @@ class TypeDeclBlock extends DeclarationBlock {
|
||||
this.parsed.constructors = group(body, 'constructor', ConstructorBlock.parseRE, markers.constructor, false, ConstructorBlock);
|
||||
/** @type {InitialiserBlock[]} */
|
||||
this.parsed.initialisers = group(body, 'initialiser', InitialiserBlock.parseRE, markers.initialiser, false, InitialiserBlock);
|
||||
// anything other than types, fields, methods, constructors and initialisers are errors
|
||||
// anything other than types, fields, methods, constructors, enums and initialisers are errors
|
||||
/** @type {TextBlock[]} */
|
||||
this.parsed.errors = group(body, 'type-body-error', /[^{}ZFGCE\s]/g, markers.error);
|
||||
this.parsed.errors = group(body, 'type-body-error', /[^{}ZFGCEU\s;]+/g, markers.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,6 +875,10 @@ class ModuleBlock extends TextBlockArray {
|
||||
return this._ensureParsed().types;
|
||||
}
|
||||
|
||||
get parseErrors() {
|
||||
return this._ensureParsed().errors;
|
||||
}
|
||||
|
||||
_ensureParsed() {
|
||||
if (this._parsed) {
|
||||
return this._parsed;
|
||||
@@ -848,11 +889,14 @@ class ModuleBlock extends TextBlockArray {
|
||||
parseTypeArgs(this);
|
||||
parseAnnotations(this);
|
||||
parseEITDecls(this);
|
||||
const types = parseTypeDecls(this);
|
||||
const types = parseTypeDecls(this, null, this);
|
||||
// anything that's not a package, import or type declaration is an error
|
||||
const errors = group(this, 'module-errors', /[^NOZ;\s]+/g, ' ');
|
||||
return this._parsed = {
|
||||
packages,
|
||||
imports,
|
||||
types,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -912,10 +956,17 @@ function parseEITDecls(sourceblocks) {
|
||||
|
||||
/**
|
||||
* @param {TextBlockArray} sourceblocks
|
||||
* @return {TypeDeclBlock[]}
|
||||
* @param {TypeDeclBlock} outer_type
|
||||
* @param {ModuleBlock} mod
|
||||
*/
|
||||
function parseTypeDecls(sourceblocks) {
|
||||
return group(sourceblocks, 'type-decl', TypeDeclBlock.parseRE, markers.typeDecl, false, TypeDeclBlock);
|
||||
function parseTypeDecls(sourceblocks, outer_type, mod) {
|
||||
/** @type {TypeDeclBlock[]} */
|
||||
const typedecls = group(sourceblocks, 'type-decl', TypeDeclBlock.parseRE, markers.typeDecl, false, TypeDeclBlock);
|
||||
typedecls.forEach(td => {
|
||||
td.outer_type = outer_type;
|
||||
td.mod = mod;
|
||||
});
|
||||
return typedecls;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
const Declaration = require('./declaration');
|
||||
const ParseProblem = require('./parse-problem');
|
||||
const Token = require('./token');
|
||||
const TypeParameters = require('./type-parameters');
|
||||
|
||||
/**
|
||||
* @typedef {import('./modifier')} Modifier
|
||||
@@ -34,35 +32,6 @@ class ImportDeclaration extends Declaration {
|
||||
lastToken() {
|
||||
return this.semicolon || this.asterisk || this.nameparts.slice(-1)[0];
|
||||
}
|
||||
|
||||
validate() {
|
||||
const checkModifierIsStatic = () => {
|
||||
if (this.static_ && this.static_.text !== 'static') {
|
||||
return ParseProblem.syntaxError(this.static_);
|
||||
}
|
||||
}
|
||||
|
||||
const checkNoInvalidModifiers = () => {
|
||||
return this.modifiers.map(modifier => {
|
||||
if (modifier instanceof Token) {
|
||||
return ParseProblem.syntaxError(modifier);
|
||||
}
|
||||
if (modifier instanceof TypeParameters) {
|
||||
return ParseProblem.syntaxError(modifier.open);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** @type {ParseProblem[]} */
|
||||
const problems = [
|
||||
checkModifierIsStatic(),
|
||||
...ParseProblem.checkNonKeywordIdents(this.nameparts),
|
||||
ParseProblem.checkSemicolon(this),
|
||||
...checkNoInvalidModifiers(),
|
||||
];
|
||||
|
||||
return problems;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ImportDeclaration;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
/**
|
||||
* @typedef {import('java-mti').JavaType} JavaType
|
||||
*/
|
||||
const { ImportBlock } = require('../parser9');
|
||||
|
||||
/**
|
||||
* Class representing a resolved import.
|
||||
*
|
||||
* Each instance holds an array of types that would be resolved by the specified import.
|
||||
* Each type is mapped to an MTI which lists the implementation details of the type (fields, methods, etc).
|
||||
* Each type is mapped to a JavaType which lists the implementation details of the type (fields, methods, etc).
|
||||
*
|
||||
*/
|
||||
class ResolvedImport {
|
||||
/**
|
||||
* @param {ImportBlock} import_decl
|
||||
* @param {RegExpMatchArray} matches
|
||||
* @param {Map<string,import('../mti').Type>} typemap
|
||||
* @param {Map<string,JavaType>} typemap
|
||||
* @param {'owner-package'|'import'|'implicit-import'} import_kind
|
||||
*/
|
||||
constructor(import_decl, matches, typemap, import_kind) {
|
||||
@@ -27,7 +30,7 @@ const { ImportBlock } = require('../parser9');
|
||||
this.fullyQualifiedNames = Array.from(matches);
|
||||
|
||||
/**
|
||||
* THe map of fully-qualified type names to MTIs
|
||||
* THe map of fully-qualified type names to JavaTypes
|
||||
*/
|
||||
this.types = new Map(matches.map(name => [name, typemap.get(name)]));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @typedef {import('./token')} Token
|
||||
* @typedef {import('./type')} TypeDeclaration
|
||||
* @typedef {import('java-mti').JavaType} JavaType
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -78,8 +79,8 @@ class ResolvedType {
|
||||
error = '';
|
||||
|
||||
/**
|
||||
* The resolved MTIs that match this type. This will be an empty array if the type cannot be found.
|
||||
* @type {import('../mti').Type[]}
|
||||
* The resolved JavaTypes that match this type. This will be an empty array if the type cannot be found.
|
||||
* @type {JavaType[]}
|
||||
*/
|
||||
mtis = [];
|
||||
|
||||
|
||||
182
langserver/java/source-type.js
Normal file
182
langserver/java/source-type.js
Normal file
@@ -0,0 +1,182 @@
|
||||
const { JavaType, CEIType, Method, Field, Parameter, TypeVariable, UnresolvedType } = require('java-mti');
|
||||
const { ModuleBlock, TypeDeclBlock, FieldBlock, MethodBlock, ParameterBlock, TextBlock } = require('./parser9');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{modifiers:TextBlock[]}} x
|
||||
*/
|
||||
function mapmods(x) {
|
||||
return x.modifiers.map(m => m.source);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {TextBlock} decl
|
||||
*/
|
||||
function extractTypeList(decl) {
|
||||
if (!decl) {
|
||||
return [];
|
||||
}
|
||||
const types = [];
|
||||
const re = /[WD]( *[WDT.])*/g;
|
||||
const declba = decl.blockArray();
|
||||
const sm = declba.sourcemap();
|
||||
for (let m; m = re.exec(sm.simplified);) {
|
||||
const start = sm.map[m.index], end = sm.map[m.index + m[0].length-1];
|
||||
const block_range = declba.blocks.slice(start, end+1);
|
||||
types.push(block_range);
|
||||
}
|
||||
return types.map(tokens => {
|
||||
const decl = {
|
||||
type: tokens.map(t => t.source).join(''),
|
||||
typeTokens: tokens,
|
||||
}
|
||||
return new ResolvableType(decl);
|
||||
});
|
||||
}
|
||||
|
||||
class SourceType extends CEIType {
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {TypeDeclBlock} type
|
||||
* @param {string} qualified_type_name qualified $-separated type name
|
||||
*/
|
||||
constructor(mod, type, qualified_type_name) {
|
||||
super(type.shortSignature, type.kind(), mapmods(type), type.docs);
|
||||
this._decl = type;
|
||||
super.packageName = mod.packageName;
|
||||
super.simpleTypeName = type.simpleName;
|
||||
super.dottedTypeName = qualified_type_name.replace(/\$/g, '.');
|
||||
super.fullyDottedRawName = type.fullyDottedName;
|
||||
|
||||
this.extends_types = type.extends_decl ? extractTypeList(type.extends_decl) : []
|
||||
this.implements_types = type.implements_decl ? extractTypeList(type.implements_decl) : [];
|
||||
this.implicit_extend = !this.extends_types.length && !this.implements_types.length ? [new ResolvableType({type: 'java.lang.Object', typeTokens:[]})] : [];
|
||||
|
||||
this.fields = type.fields.map(f => new SourceField(this, f));
|
||||
this.methods = type.methods.map(m => new SourceMethod(this, m));
|
||||
super.typevars = type.typevars.map(tv => {
|
||||
const typevar = new TypeVariable(tv.name);
|
||||
// automatically add the Object bound
|
||||
typevar.bounds.push(new TypeVariable.Bound(this, 'Ljava/lang/Object;', false));
|
||||
return typevar;
|
||||
});
|
||||
}
|
||||
|
||||
get supers() {
|
||||
return [
|
||||
...this.implicit_extend.map(t => t.resolved),
|
||||
...this.extends_types.map(t => t.resolved),
|
||||
...this.implements_types.map(t => t.resolved)
|
||||
];
|
||||
}
|
||||
|
||||
getAllResolvableTypes() {
|
||||
/** @type {ResolvableType[]} */
|
||||
const res = [
|
||||
...this.implicit_extend,
|
||||
...this.extends_types,
|
||||
...this.implements_types,
|
||||
];
|
||||
this.fields.forEach(f => res.push(f._type));
|
||||
this.methods.forEach(m => {
|
||||
res.push(m._returnType);
|
||||
m.parameters.forEach(p => res.push(p._paramType));
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceField extends Field {
|
||||
/**
|
||||
* @param {SourceType} owner
|
||||
* @param {FieldBlock} decl
|
||||
*/
|
||||
constructor(owner, decl) {
|
||||
super(mapmods(decl), decl.docs);
|
||||
this._owner = owner;
|
||||
this._decl = decl;
|
||||
this._type = new ResolvableType(decl);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._decl.name;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this._type.resolved;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceMethod extends Method {
|
||||
/**
|
||||
* @param {SourceType} owner
|
||||
* @param {MethodBlock} decl
|
||||
*/
|
||||
constructor(owner, decl) {
|
||||
super(mapmods(decl), decl.docs);
|
||||
this._owner = owner;
|
||||
this._decl = decl;
|
||||
this._parameters = decl.parameters.map((p,i) => new SourceParameter(p));
|
||||
this._returnType = new ResolvableType(decl);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._decl.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {SourceParameter[]}
|
||||
*/
|
||||
get parameters() {
|
||||
return this._parameters;
|
||||
}
|
||||
|
||||
get returnType() {
|
||||
return this._returnType.resolved;
|
||||
}
|
||||
}
|
||||
|
||||
class SourceParameter extends Parameter {
|
||||
/**
|
||||
* @param {ParameterBlock} decl
|
||||
* @param {ResolvableType} [type]
|
||||
*/
|
||||
constructor(decl, type = new ResolvableType(decl)) {
|
||||
super(decl.name, type, decl.isVarArgs);
|
||||
this._decl = decl;
|
||||
this._paramType = type;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this._paramType.resolved;
|
||||
}
|
||||
}
|
||||
|
||||
class ResolvableType extends UnresolvedType {
|
||||
/**
|
||||
*
|
||||
* @param {{type:string, typeTokens:TextBlock[]}} decl
|
||||
*/
|
||||
constructor(decl) {
|
||||
super(decl.type);
|
||||
this._decl = decl;
|
||||
/** @type {import('../java/parsetypes/resolved-type')} */
|
||||
this._resolved = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {JavaType}
|
||||
*/
|
||||
get resolved() {
|
||||
if (!this._resolved) return this;
|
||||
if (!this._resolved.mtis.length) return this;
|
||||
return this._resolved.mtis[0];
|
||||
}
|
||||
|
||||
get typeTokens() {
|
||||
return this._decl.typeTokens;
|
||||
}
|
||||
}
|
||||
|
||||
exports.SourceType = SourceType;
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* @typedef {Map<string,JavaType>} TypeMap
|
||||
*/
|
||||
const { JavaType, PrimitiveType, ArrayType, CEIType } = require('java-mti');
|
||||
const { ResolvedImport } = require('./import-resolver');
|
||||
const MTI = require('./mti');
|
||||
const ResolvedType = require('./parsetypes/resolved-type');
|
||||
|
||||
/**
|
||||
@@ -85,9 +88,6 @@ function createTypeScopeRegex(fully_qualified_scope, dotted_raw_typename) {
|
||||
// split the type name across enclosed type boundaries
|
||||
const scopes = fully_qualified_scope.split('$');
|
||||
|
||||
// the first scope is the dotted package name and top-level type - we need to escape the package-qualifier dots for regex
|
||||
scopes[0] = scopes[0].replace(/\./g,'[.]');
|
||||
|
||||
// if the typename we are searching represents an enclosed type, the type-qualifier dots must be replaced with $
|
||||
const enclosed_raw_typename = dotted_raw_typename.replace(/\./g,'[$]');
|
||||
|
||||
@@ -102,21 +102,21 @@ function createTypeScopeRegex(fully_qualified_scope, dotted_raw_typename) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate MTIs that match a type.
|
||||
* @param {string} typename The type to resolve
|
||||
* Locate JavaTypes that match a type label.
|
||||
* @param {string} type_label The type to resolve
|
||||
* @param {string} fully_qualified_scope The fully-qualified JRE name of the current type scope.
|
||||
* @param {ResolvedImport[]} resolved_imports The list of types resolved from the imports
|
||||
* @param {Map<string,MTI.Type>} typemap the global list of types
|
||||
* @param {TypeMap} typemap
|
||||
*/
|
||||
function resolveType(typename, fully_qualified_scope, resolved_imports, typemap) {
|
||||
const { type, error } = parse_type(typename);
|
||||
function resolveType(type_label, fully_qualified_scope, resolved_imports, typemap) {
|
||||
const { type, error } = parse_type(type_label);
|
||||
if (error) {
|
||||
// don't try to find the type if the parsing failed
|
||||
type.error = error;
|
||||
return type;
|
||||
}
|
||||
|
||||
// locate the MTIs for the type and type arguments
|
||||
// locate the JavaTypes for the type and type arguments
|
||||
resolveCompleteType(type, fully_qualified_scope, resolved_imports, typemap);
|
||||
return type;
|
||||
}
|
||||
@@ -126,11 +126,11 @@ function resolveType(typename, fully_qualified_scope, resolved_imports, typemap)
|
||||
* @param {ResolvedType} type
|
||||
* @param {string} fully_qualified_scope
|
||||
* @param {ResolvedImport[]} resolved_imports
|
||||
* @param {Map<string,MTI.Type>} typemap
|
||||
* @param {TypeMap} typemap
|
||||
*/
|
||||
function resolveCompleteType(type, fully_qualified_scope, resolved_imports, typemap) {
|
||||
|
||||
type.mtis = findTypeMTIs(type.getDottedRawType(), type.arrdims, fully_qualified_scope, resolved_imports, typemap);
|
||||
type.mtis = findJavaTypes(type.getDottedRawType(), type.arrdims, fully_qualified_scope, resolved_imports, typemap);
|
||||
|
||||
// resolve type arguments
|
||||
type.parts.filter(p => p.typeargs).forEach(p => {
|
||||
@@ -146,34 +146,34 @@ function resolveCompleteType(type, fully_qualified_scope, resolved_imports, type
|
||||
* @param {number} arraydims
|
||||
* @param {string} fully_qualified_scope The fully-qualified JRE name of the current type scope.
|
||||
* @param {ResolvedImport[]} resolved_imports The list of types resolved from the imports
|
||||
* @param {Map<string,MTI.Type>} typemap
|
||||
* @param {TypeMap} typemap
|
||||
*/
|
||||
function findTypeMTIs(dotted_raw_typename, arraydims, fully_qualified_scope, resolved_imports, typemap) {
|
||||
let mtis = findRawTypeMTIs(dotted_raw_typename, fully_qualified_scope, resolved_imports, typemap);
|
||||
function findJavaTypes(dotted_raw_typename, arraydims, fully_qualified_scope, resolved_imports, typemap) {
|
||||
let types = findRawJavaTypes(dotted_raw_typename, fully_qualified_scope, resolved_imports, typemap);
|
||||
|
||||
if (arraydims > 0) {
|
||||
// convert matches to array MTIs
|
||||
mtis.forEach((mti,idx,arr) => {
|
||||
arr[idx] = MTI.makeArrayType(mti, arraydims);
|
||||
})
|
||||
// convert matches to array types
|
||||
const array_types = types.map(t => new ArrayType(t, arraydims));
|
||||
return array_types;
|
||||
}
|
||||
|
||||
return mtis;
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a dotted type name to one or more MTIs
|
||||
* Match a dotted type name to one or more JavaTypes
|
||||
* @param {string} dotted_raw_typename
|
||||
* @param {string} fully_qualified_scope The fully-qualified JRE name of the current type scope.
|
||||
* @param {Map<string,MTI.Type>} typemap
|
||||
* @param {TypeMap} typemap
|
||||
* @param {ResolvedImport[]} resolved_imports The list of types resolved from the imports
|
||||
* @returns {(PrimitiveType|CEIType)[]}
|
||||
*/
|
||||
function findRawTypeMTIs(dotted_raw_typename, fully_qualified_scope, resolved_imports, typemap) {
|
||||
function findRawJavaTypes(dotted_raw_typename, fully_qualified_scope, resolved_imports, typemap) {
|
||||
|
||||
// first check if it's a simple primitive
|
||||
if (/^(int|char|boolean|void|long|byte|short|float|double)$/.test(dotted_raw_typename)) {
|
||||
if (PrimitiveType.isPrimitiveTypeName(dotted_raw_typename)) {
|
||||
// return the primitive type
|
||||
return [MTI.fromPrimitive(dotted_raw_typename)];
|
||||
return [PrimitiveType.fromName(dotted_raw_typename)];
|
||||
}
|
||||
|
||||
// create a regex to search for the type name
|
||||
@@ -190,7 +190,7 @@ function findRawTypeMTIs(dotted_raw_typename, fully_qualified_scope, resolved_im
|
||||
// if the type was not found in the current type scope, construct a new search for the imported types.
|
||||
// - since we don't know if the type name includes package qualifiers or not, this regex allows for implicit
|
||||
// package prefixes (todo - need to figure out static type imports)
|
||||
search = new RegExp(`^(.+?[.])?${dotted_raw_typename.replace(/\./g,'[.$]')}$`);
|
||||
search = new RegExp(`^(.+?/)?${dotted_raw_typename.replace(/\./g,'[/$]')}$`);
|
||||
|
||||
// search the imports for the type
|
||||
matched_types =
|
||||
@@ -211,7 +211,7 @@ function findRawTypeMTIs(dotted_raw_typename, fully_qualified_scope, resolved_im
|
||||
|
||||
if (!matched_types.length) {
|
||||
// if the type doesn't match any import, the final option is a fully qualified match across all types in all libraries
|
||||
search = new RegExp(`^${dotted_raw_typename.replace(/\./g,'[.$]')}$`);
|
||||
search = new RegExp(`^${dotted_raw_typename.replace(/\./g,'[/$]')}$`);
|
||||
for (let typename of typemap.keys()) {
|
||||
if (search.test(typename)) {
|
||||
matched_types = [{
|
||||
@@ -227,7 +227,8 @@ function findRawTypeMTIs(dotted_raw_typename, fully_qualified_scope, resolved_im
|
||||
// - if the matched_types array is empty, the type is not found
|
||||
// - if the matched_type array has more than one entry, the type matches types across multiple imports
|
||||
// - if the matched_type array has one entry and multiple MTIs, the type matches multiple types in a single import
|
||||
return matched_types.reduce((mtis,mt) => [...mtis, ...mt.mtis] , []);
|
||||
return matched_types
|
||||
.reduce((types, type) => [...types, ...type.mtis] , [])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +236,7 @@ function findRawTypeMTIs(dotted_raw_typename, fully_qualified_scope, resolved_im
|
||||
* @param {string[]} types
|
||||
* @param {string} fully_qualified_scope the JRE name of the type scope we are resolving in
|
||||
* @param {ResolvedImport[]} resolved_imports the list of resolved imports (and types associated with them)
|
||||
* @param {Map<string,MTI.Type>} typemap
|
||||
* @param {TypeMap} typemap
|
||||
*/
|
||||
function resolveTypes(types, fully_qualified_scope, resolved_imports, typemap) {
|
||||
return types.map(typename => resolveType(typename, fully_qualified_scope, resolved_imports, typemap));
|
||||
@@ -246,7 +247,7 @@ function resolveTypes(types, fully_qualified_scope, resolved_imports, typemap) {
|
||||
* @param {import('./parsetypes/typeident')[]} types
|
||||
* @param {string} fully_qualified_scope the JRE name of the type scope we are resolving in
|
||||
* @param {ResolvedImport[]} resolved_imports the list of resolved imports (and types associated with them)
|
||||
* @param {Map<string,MTI.Type>} typemap
|
||||
* @param {TypeMap} typemap
|
||||
*/
|
||||
function resolveTypeIdents(types, fully_qualified_scope, resolved_imports, typemap) {
|
||||
const names = types.map(typeident =>
|
||||
@@ -258,6 +259,7 @@ function resolveTypeIdents(types, fully_qualified_scope, resolved_imports, typem
|
||||
|
||||
module.exports = {
|
||||
parse_type,
|
||||
resolveType,
|
||||
resolveTypes,
|
||||
resolveTypeIdents,
|
||||
ResolvedType,
|
||||
|
||||
@@ -1,36 +1,59 @@
|
||||
const { ModuleBlock, TypeDeclBlock } = require('./parser9');
|
||||
const { resolveImports } = require('../java/import-resolver');
|
||||
const MTI = require('./mti');
|
||||
const ResolvedImport = require('../java/parsetypes/resolved-import');
|
||||
const { resolveType } = require('../java/type-resolver');
|
||||
const { SourceType } = require('./source-type');
|
||||
const { JavaType } = require('java-mti');
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} package_name
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {string} owner_typename
|
||||
* @param {ModuleBlock|TypeDeclBlock} parent
|
||||
* @param {MTI.Type[]} mtis
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
function getSourceMTIs(package_name, owner_typename, parent, mtis) {
|
||||
function getSourceTypes(mod, owner_typename, parent, source_types) {
|
||||
parent.types.forEach(type => {
|
||||
const mods = type.modifiers.map(m => m.source);
|
||||
const qualifiedTypeName = `${owner_typename}${type.simpleName}`;
|
||||
// we add the names of type variables here, but we resolve any bounds later
|
||||
const typevar_names = type.typevars.map(tv => tv.name);
|
||||
const mti = new MTI().addType(package_name, '', mods, type.kind(), qualifiedTypeName, typevar_names);
|
||||
mtis.push(mti);
|
||||
getSourceMTIs(package_name, `${qualifiedTypeName}$`, type, mtis);
|
||||
//const typevar_names = type.typevars.map(tv => tv.name);
|
||||
//const mti = new MTI().addType(package_name, '', mods, type.kind(), qualifiedTypeName, typevar_names);
|
||||
const t = new SourceType(mod, type, qualifiedTypeName);
|
||||
source_types.push(t);
|
||||
getSourceTypes(mod, `${qualifiedTypeName}$`, type, source_types);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SourceType} source_type
|
||||
* @param {ResolvedImport[]} resolved_imports
|
||||
* @param {Map<string,JavaType>} typemap
|
||||
*/
|
||||
function resolveResolvableTypes(source_type, resolved_imports, typemap) {
|
||||
const fully_qualified_scope = source_type.shortSignature;
|
||||
const resolvableTypes = source_type.getAllResolvableTypes();
|
||||
resolvableTypes.forEach(rt => {
|
||||
rt._resolved = resolveType(rt.label, fully_qualified_scope, resolved_imports, typemap);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {Map<string, JavaType>} androidLibrary
|
||||
*/
|
||||
function validate(mod, androidLibrary) {
|
||||
console.time('validation');
|
||||
|
||||
const source_mtis = [];
|
||||
getSourceMTIs(mod.packageName, '', mod, source_mtis);
|
||||
/** @type {SourceType[]} */
|
||||
const source_types = [];
|
||||
getSourceTypes(mod, '', mod, source_types);
|
||||
|
||||
const imports = resolveImports(androidLibrary, mod.imports, mod.packageName, source_mtis);
|
||||
const imports = resolveImports(androidLibrary, source_types, mod.imports, mod.packageName);
|
||||
|
||||
source_types.forEach(t => {
|
||||
resolveResolvableTypes(t, imports.resolved, imports.typemap);
|
||||
});
|
||||
|
||||
const module_validaters = [
|
||||
require('./validation/multiple-package-decls'),
|
||||
@@ -39,10 +62,15 @@ function validate(mod, androidLibrary) {
|
||||
require('./validation/parse-errors'),
|
||||
require('./validation/modifier-errors'),
|
||||
require('./validation/unresolved-imports'),
|
||||
require('./validation/resolved-types'),
|
||||
require('./validation/unresolved-types'),
|
||||
require('./validation/invalid-types'),
|
||||
require('./validation/bad-extends'),
|
||||
require('./validation/bad-implements'),
|
||||
require('./validation/non-implemented-interfaces'),
|
||||
require('./validation/bad-overrides'),
|
||||
];
|
||||
let problems = [
|
||||
module_validaters.map(v => v(mod, imports)),
|
||||
module_validaters.map(v => v(mod, imports, source_types)),
|
||||
];
|
||||
console.timeEnd('validation');
|
||||
|
||||
|
||||
54
langserver/java/validation/bad-extends.js
Normal file
54
langserver/java/validation/bad-extends.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const { ModuleBlock, TypeDeclBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const {SourceType} = require('../source-type');
|
||||
|
||||
/**
|
||||
* @param {SourceType} source_type
|
||||
* @param {*} probs
|
||||
*/
|
||||
function checkExtends(source_type, probs) {
|
||||
if (source_type.extends_types.length === 0) {
|
||||
return;
|
||||
}
|
||||
const supertypes = source_type.extends_types.map(st => st.resolved);
|
||||
const supertype = supertypes[0];
|
||||
if (source_type.typeKind === 'enum') {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[0].typeTokens, `Enum types cannot declare a superclass`));
|
||||
}
|
||||
if (source_type.typeKind === 'class' && supertypes.length > 1) {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[1].typeTokens, `Class types cannot inherit from more than one type`));
|
||||
}
|
||||
if (source_type.typeKind === 'class' && supertype.typeKind !== 'class') {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[0].typeTokens, `Class '${source_type.fullyDottedRawName}' cannot inherit from ${supertype.typeKind} type: '${supertype.fullyDottedRawName}'`));
|
||||
}
|
||||
if (source_type.typeKind === 'class' && supertype.typeKind === 'class' && supertype.modifiers.includes('final')) {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[0].typeTokens, `Class '${source_type.fullyDottedRawName}' cannot inherit from final class: '${supertype.fullyDottedRawName}'`));
|
||||
}
|
||||
if (source_type.typeKind === 'class' && supertype === source_type) {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[0].typeTokens, `Class '${source_type.fullyDottedRawName}' cannot inherit from itself`));
|
||||
}
|
||||
if (source_type.typeKind === 'interface') {
|
||||
supertypes.forEach((supertype, i) => {
|
||||
if (supertype.typeKind !== 'interface') {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[i].typeTokens, `Interface '${source_type.fullyDottedRawName}' cannot inherit from ${supertype.typeKind} type: '${supertype.fullyDottedRawName}'`));
|
||||
}
|
||||
if (supertype === source_type) {
|
||||
probs.push(ParseProblem.Error(source_type.extends_types[i].typeTokens, `Interface '${source_type.fullyDottedRawName}' cannot inherit from itself`));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {*} imports
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
module.exports = function(mod, imports, source_types) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
source_types.forEach(type => checkExtends(type, probs));
|
||||
|
||||
return probs;
|
||||
}
|
||||
42
langserver/java/validation/bad-implements.js
Normal file
42
langserver/java/validation/bad-implements.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { ModuleBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const {SourceType} = require('../source-type');
|
||||
const { UnresolvedType } = require('java-mti');
|
||||
|
||||
/**
|
||||
* @param {SourceType} source_type
|
||||
* @param {*} probs
|
||||
*/
|
||||
function checkImplements(source_type, probs) {
|
||||
if (source_type.implements_types.length === 0) {
|
||||
return;
|
||||
}
|
||||
const interfaces = source_type.implements_types.map(it => it.resolved);
|
||||
if (source_type.typeKind === 'interface') {
|
||||
probs.push(ParseProblem.Error(source_type.implements_types[0].typeTokens, `Interface types cannot declare an implements section`));
|
||||
}
|
||||
if (source_type.typeKind === 'class') {
|
||||
interfaces.forEach((intf, i) => {
|
||||
if (intf instanceof UnresolvedType) {
|
||||
return;
|
||||
}
|
||||
if (intf.typeKind !== 'interface') {
|
||||
probs.push(ParseProblem.Error(source_type.implements_types[i].typeTokens, `Class '${source_type.fullyDottedRawName}' cannot implement ${intf.typeKind} type: '${intf.fullyDottedRawName}'`));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {*} imports
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
module.exports = function(mod, imports, source_types) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
source_types.forEach(type => checkImplements(type, probs));
|
||||
|
||||
return probs;
|
||||
}
|
||||
72
langserver/java/validation/bad-overrides.js
Normal file
72
langserver/java/validation/bad-overrides.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const { ModuleBlock, TextBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const {SourceType} = require('../source-type');
|
||||
const {CEIType, Method} = require('java-mti');
|
||||
|
||||
function nonAbstractLabel(label) {
|
||||
return label.replace(/\babstract /g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SourceType} source_type
|
||||
* @param {*} probs
|
||||
*/
|
||||
function checkOverrides(source_type, probs) {
|
||||
if (source_type.extends_types.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (source_type.typeKind !== 'class') {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {{ann:TextBlock, method:Method, method_id:string}[]} */
|
||||
const overriden_methods = [];
|
||||
source_type.methods.reduce((arr, method) => {
|
||||
const ann = method._decl.annotations.find(a => /^@\s*Override$/.test(a.source));
|
||||
if (ann) {
|
||||
arr.push({
|
||||
ann,
|
||||
method,
|
||||
method_id: `${method.name}${method.methodSignature}`,
|
||||
})
|
||||
}
|
||||
return arr;
|
||||
}, overriden_methods);
|
||||
|
||||
if (!overriden_methods.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const methods = new Set(), supers_done = new Set();
|
||||
const supers = source_type.supers.slice();
|
||||
while (supers.length) {
|
||||
const s = supers.shift();
|
||||
supers_done.add(s);
|
||||
s.methods.forEach(m => {
|
||||
methods.add(`${m.name}${m.methodSignature}`);
|
||||
});
|
||||
if (s instanceof CEIType) {
|
||||
s.supers.filter(s => !supers_done.has(s)).forEach(s => supers.push(s));
|
||||
}
|
||||
}
|
||||
|
||||
overriden_methods.forEach(x => {
|
||||
if (!methods.has(x.method_id)) {
|
||||
probs.push(ParseProblem.Error(x.ann, `${x.method.label} does not override a matching method in any inherited type or interface`));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {*} imports
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
module.exports = function(mod, imports, source_types) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
source_types.forEach(type => checkOverrides(type, probs));
|
||||
|
||||
return probs;
|
||||
}
|
||||
49
langserver/java/validation/invalid-types.js
Normal file
49
langserver/java/validation/invalid-types.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const { ModuleBlock, TypeDeclBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const {SourceType} = require('../source-type');
|
||||
const {JavaType, CEIType, TypeArgument, UnresolvedType} = require('java-mti')
|
||||
|
||||
/**
|
||||
* @param {JavaType} type
|
||||
*/
|
||||
function checkType(type, is_return_type, typeTokens, probs) {
|
||||
if (type instanceof UnresolvedType) {
|
||||
return;
|
||||
}
|
||||
const typesig = type.typeSignature;
|
||||
if (typesig === 'V' && !is_return_type) {
|
||||
probs.push(ParseProblem.Error(typeTokens, `'void' is not a valid type for variables`))
|
||||
}
|
||||
if (/^\[+V/.test(typesig)) {
|
||||
probs.push(ParseProblem.Error(typeTokens, `Illegal type: '${type.label}'`))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SourceType} type
|
||||
* @param {*} probs
|
||||
*/
|
||||
function checkInvalidTypes(type, probs) {
|
||||
type.fields.forEach(f => checkType(f.type, false, f._decl.typeTokens, probs));
|
||||
type.methods.forEach(m => {
|
||||
checkType(m.returnType, true, m._decl.typeTokens, probs);
|
||||
m.parameters.forEach(p => {
|
||||
checkType(p.type, false, p._decl.typeTokens, probs);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {*} imports
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
module.exports = function(mod, imports, source_types) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
source_types.forEach(type => checkInvalidTypes(type, probs));
|
||||
|
||||
return probs;
|
||||
}
|
||||
@@ -16,10 +16,6 @@ function checkDuplicate(mods, probs) {
|
||||
m.set(mod.source, mod);
|
||||
} else {
|
||||
probs.push(ParseProblem.Error(mod, 'Duplicate modifier'));
|
||||
if (firstmod !== null) {
|
||||
probs.push(ParseProblem.Error(firstmod, 'Duplicate modifier'));
|
||||
m.set(mod.source, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,13 +68,15 @@ function checkFieldModifiers(field, probs) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Set<string>} ownertypemods
|
||||
* @param {TypeDeclBlock} type
|
||||
* @param {Map<string,*>} ownertypemods
|
||||
* @param {MethodBlock} method
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function checkMethodModifiers(ownertypemods, method, probs) {
|
||||
function checkMethodModifiers(type, ownertypemods, method, probs) {
|
||||
checkDuplicate(method.modifiers, probs);
|
||||
checkConflictingAccess(method.modifiers, probs);
|
||||
|
||||
const allmods = new Map(method.modifiers.map(m => [m.source, m]));
|
||||
if (allmods.has('abstract') && allmods.has('final')) {
|
||||
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations cannot be abstract and final'));
|
||||
@@ -89,10 +87,10 @@ function checkMethodModifiers(ownertypemods, method, probs) {
|
||||
if (allmods.has('abstract') && method.body().simplified.startsWith('B')) {
|
||||
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations marked as abstract cannot have a method body'));
|
||||
}
|
||||
if (!allmods.has('abstract') && !allmods.has('native') && !method.body().simplified.startsWith('B')) {
|
||||
if (type.kind() !== 'interface' && !allmods.has('abstract') && !allmods.has('native') && !method.body().simplified.startsWith('B')) {
|
||||
probs.push(ParseProblem.Error(method, `Method '${method.name}' must have an implementation or be defined as abstract or native`));
|
||||
}
|
||||
if (allmods.has('abstract') && !ownertypemods.has('abstract')) {
|
||||
if (type.kind() !== 'interface' && allmods.has('abstract') && !ownertypemods.has('abstract')) {
|
||||
probs.push(ParseProblem.Error(method, `Method '${method.name}' cannot be declared abstract inside a non-abstract type`));
|
||||
}
|
||||
if (allmods.has('native') && method.body().simplified.startsWith('B')) {
|
||||
@@ -119,9 +117,27 @@ function checkInitialiserModifiers(initialiser, probs) {
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function checkTypeModifiers(type, probs) {
|
||||
const typemods = new Set(type.modifiers.map(m => m.source));
|
||||
const typemods = new Map(type.modifiers.map(m => [m.source, m]));
|
||||
checkDuplicate(type.modifiers, probs);
|
||||
|
||||
if (type.kind() === 'interface' && typemods.has('final')) {
|
||||
probs.push(ParseProblem.Error(typemods.get('final'), 'Interface declarations cannot be marked as final'));
|
||||
}
|
||||
if (type.kind() === 'enum' && typemods.has('abstract')) {
|
||||
probs.push(ParseProblem.Error(typemods.get('abstract'), 'Enum declarations cannot be marked as abstract'));
|
||||
}
|
||||
// top-level types cannot be private, protected or static
|
||||
for (let mod of ['private','protected', 'static']) {
|
||||
if (!type.outer_type && typemods.has(mod)) {
|
||||
probs.push(ParseProblem.Error(typemods.get(mod), `Top-level declarations cannot be marked as ${mod}`));
|
||||
}
|
||||
}
|
||||
if (type.outer_type) {
|
||||
checkConflictingAccess(type.modifiers, probs);
|
||||
}
|
||||
|
||||
type.fields.forEach(field => checkFieldModifiers(field, probs));
|
||||
type.methods.forEach(method => checkMethodModifiers(typemods, method, probs));
|
||||
type.methods.forEach(method => checkMethodModifiers(type, typemods, method, probs));
|
||||
type.constructors.forEach(ctr => checkConstructorModifiers(ctr, probs));
|
||||
//type.initialisers.forEach(initer => checkInitModifiers(initer, probs));
|
||||
// check enclosed types
|
||||
|
||||
64
langserver/java/validation/non-implemented-interfaces.js
Normal file
64
langserver/java/validation/non-implemented-interfaces.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const { ModuleBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const { SourceType } = require('../source-type');
|
||||
const {CEIType} = require('java-mti');
|
||||
|
||||
function nonAbstractLabel(label) {
|
||||
return label.replace(/\babstract /g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SourceType} source_type
|
||||
* @param {*} probs
|
||||
*/
|
||||
function checkImplementedInterfaces(source_type, probs) {
|
||||
if (source_type.implements_types.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (source_type.typeKind === 'interface') {
|
||||
return;
|
||||
}
|
||||
if (source_type.modifiers.includes('abstract')) {
|
||||
return;
|
||||
}
|
||||
const interfaces = new Set(), supers_done = new Set();
|
||||
const supers = source_type.supers.slice();
|
||||
while (supers.length) {
|
||||
const s = supers.shift();
|
||||
supers_done.add(s);
|
||||
if (s.typeKind === 'interface') {
|
||||
interfaces.add(s);
|
||||
}
|
||||
if (s instanceof CEIType) {
|
||||
s.supers.filter(s => !supers_done.has(s)).forEach(s => supers.push(s));
|
||||
}
|
||||
}
|
||||
|
||||
const implemented = source_type.methods.map(m => `${m.name}${m.methodSignature}`);
|
||||
interfaces.forEach((intf, i) => {
|
||||
const missing_methods = [];
|
||||
intf.methods.forEach(m => {
|
||||
const namedsig = `${m.name}${m.methodSignature}`
|
||||
if (implemented.indexOf(namedsig) < 0) {
|
||||
missing_methods.push(nonAbstractLabel(m.label));
|
||||
}
|
||||
})
|
||||
if (missing_methods.length) {
|
||||
probs.push(ParseProblem.Error(source_type._decl.kindToken, `Non-abstract ${source_type.typeKind} '${source_type.fullyDottedRawName}' does not implement the following methods from interface '${intf.fullyDottedRawName}':\n${missing_methods.join('\n')}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {*} imports
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
module.exports = function(mod, imports, source_types) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
source_types.forEach(type => checkImplementedInterfaces(type, probs));
|
||||
|
||||
return probs;
|
||||
}
|
||||
@@ -24,6 +24,7 @@ function checkMethodParseErrors(method, probs) {
|
||||
*/
|
||||
module.exports = function(mod) {
|
||||
const probs = [];
|
||||
mod.parseErrors.forEach(err => probs.push(ParseProblem.Error(err, `Invalid, incomplete or unsupported declaration`)));
|
||||
mod.types.forEach(type => checkTypeParseErrors(type, probs));
|
||||
return probs;
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
/**
|
||||
* @typedef {import('../parsetypes/resolved-import')} ResolvedImport
|
||||
*/
|
||||
const { ModuleBlock, TypeDeclBlock, DeclaredVariableBlock, MethodBlock, ParameterBlock, TextBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const { resolveTypes } = require('../type-resolver');
|
||||
const ResolvedType = require('../parsetypes/resolved-type');
|
||||
|
||||
/**
|
||||
* @param {DeclaredVariableBlock|MethodBlock|TextBlock[] & {typeTokens: *[]}} decl
|
||||
* @param {ResolvedType|ResolvedType[]} resolved
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function checkResolvedTypes(decl, resolved, probs) {
|
||||
if (Array.isArray(resolved)) {
|
||||
resolved.forEach(resolved => checkResolvedTypes(decl, resolved, probs));
|
||||
return;
|
||||
}
|
||||
if (resolved.error) {
|
||||
probs.push(ParseProblem.Error(decl, resolved.error));
|
||||
return;
|
||||
}
|
||||
// the parser will detect varargs (...) on all variable declarations
|
||||
if (decl instanceof DeclaredVariableBlock && decl.isVarArgs && !(decl instanceof ParameterBlock)) {
|
||||
probs.push(ParseProblem.Error(decl.varBlock.varargs_token, `Variable-arity can only be applied to parameter declarations.`));
|
||||
}
|
||||
// void arrays are illegal
|
||||
if (/^void\[/.test(resolved.rawlabel)) {
|
||||
probs.push(ParseProblem.Error(decl.typeTokens, `Invalid type: ${resolved.rawlabel}`));
|
||||
return;
|
||||
}
|
||||
// void can only be used for method declarations
|
||||
if (resolved.rawlabel === 'void' && decl instanceof DeclaredVariableBlock) {
|
||||
probs.push(ParseProblem.Error(decl.typeTokens, `'void' is not a valid type for fields, parameters or variables`));
|
||||
return;
|
||||
}
|
||||
// no primitive type arguments
|
||||
if (resolved.isTypeArg && resolved.isPrimitive) {
|
||||
probs.push(ParseProblem.Error(decl.typeTokens, `Primitive types cannot be used as type arguments.`));
|
||||
return;
|
||||
}
|
||||
switch (resolved.mtis.length) {
|
||||
case 0:
|
||||
probs.push(ParseProblem.Error(decl.typeTokens, `Unresolved type: '${resolved.rawlabel}'`));
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
const matchlist = resolved.mtis.map(m => `'${m.fullyDottedRawName}'`).join(', ');
|
||||
probs.push(ParseProblem.Error(decl.typeTokens, `Ambiguous type: '${resolved.rawlabel}'. Possible matches: ${matchlist}.`));
|
||||
break;
|
||||
}
|
||||
|
||||
// check type arguments
|
||||
resolved.parts
|
||||
.filter(typepart => typepart.typeargs)
|
||||
.forEach(typepart => {
|
||||
checkResolvedTypes(decl, typepart.typeargs, probs);
|
||||
// check number of type arguments match
|
||||
if (resolved.mtis.length === 1 && typepart.typeargs.length !== resolved.mtis[0].typevars.length) {
|
||||
const msg = resolved.mtis[0].typevars.length === 0
|
||||
? `Type '${resolved.mtis[0].fullyDottedRawName}' is not declared as a parameterized type and cannot be used with type arguments.`
|
||||
: `Wrong number of type arguments for: '${resolved.mtis[0].fullyDottedRawName}'. Expected ${resolved.mtis[0].typevars.length} but found ${typepart.typeargs.length}.`;
|
||||
probs.push(ParseProblem.Error(decl.typeTokens, msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} outername
|
||||
* @param {TypeDeclBlock} owner_type
|
||||
* @param {''|'.'|'$'} qualifier
|
||||
* @param {ResolvedImport[]} resolved_imports
|
||||
* @param {Map<string, import('../mti').Type>} typemap
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function resolveFieldTypes(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
|
||||
const fieldtypes = owner_type.fields.map(f => f.type);
|
||||
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
|
||||
const resolved = resolveTypes(fieldtypes, fully_qualified_scope_name, resolved_imports, typemap);
|
||||
owner_type.fields.forEach((field,i) => {
|
||||
checkResolvedTypes(field, resolved[i], probs);
|
||||
})
|
||||
// check enclosed types
|
||||
owner_type.types.forEach(type => {
|
||||
resolveFieldTypes(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
|
||||
});
|
||||
}
|
||||
|
||||
function extractTypeList(decl) {
|
||||
if (!decl) {
|
||||
return [];
|
||||
}
|
||||
const types = [];
|
||||
const re = /[WD]( *[WDT.])*/g;
|
||||
decl = decl.blockArray();
|
||||
const sm = decl.sourcemap();
|
||||
for (let m; m = re.exec(sm.simplified);) {
|
||||
const start = sm.map[m.index], end = sm.map[m.index + m[0].length-1];
|
||||
const block_range = decl.blocks.slice(start, end+1);
|
||||
const typename = block_range.map(b => b.source).join('');
|
||||
block_range.typename = typename;
|
||||
block_range.typeTokens = block_range;
|
||||
types.push(block_range);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} outername
|
||||
* @param {TypeDeclBlock} owner_type
|
||||
* @param {''|'.'|'$'} qualifier
|
||||
* @param {ResolvedImport[]} resolved_imports
|
||||
* @param {Map<string, import('../mti').Type>} typemap
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function resolveExtends(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
|
||||
if (!owner_type.extends_token) {
|
||||
return;
|
||||
}
|
||||
// the scope for extends and implements needs to include any type variables, but not enclosed types
|
||||
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
|
||||
if (!/^(class|interface)/.test(owner_type.kind())) {
|
||||
probs.push(ParseProblem.Error(owner_type.extends_token, `extends declaration is not valid for ${owner_type.kind()} type: ${fully_qualified_scope_name}`));
|
||||
return;
|
||||
}
|
||||
const eit_types = extractTypeList(owner_type.extends_token);
|
||||
const resolved = resolveTypes(eit_types.map(x => x.typename), fully_qualified_scope_name, resolved_imports, typemap);
|
||||
eit_types.forEach((eit_type,i) => {
|
||||
checkResolvedTypes(eit_type, resolved[i], probs);
|
||||
})
|
||||
switch(owner_type.kind()) {
|
||||
case 'class':
|
||||
if (eit_types[0] && resolved[0].mtis.length === 1 && resolved[0].mtis[0].typeKind !== 'class') {
|
||||
probs.push(ParseProblem.Error(eit_types[0], `Class '${fully_qualified_scope_name}' cannot extend from ${resolved[0].mtis[0].typeKind} type '${resolved[0].mtis[0].fullyDottedRawName}'`));
|
||||
}
|
||||
if (eit_types.length > 1) {
|
||||
probs.push(ParseProblem.Error(eit_types[1], `Class types cannot extend from more than one type`));
|
||||
}
|
||||
break;
|
||||
case "interface":
|
||||
eit_types.forEach((eit_type, i) => {
|
||||
const mti = resolved[i].mtis[0];
|
||||
if (resolved[i].mtis.length === 1 && mti.typeKind !== 'interface') {
|
||||
probs.push(ParseProblem.Error(eit_type, `Interface '${fully_qualified_scope_name}' cannot extend from ${mti.typeKind} type '${mti.fullyDottedRawName}'`));
|
||||
}
|
||||
// check for repeated types
|
||||
if (resolved[i].mtis.length === 1) {
|
||||
const name = resolved[i].mtis[0].fullyDottedRawName;
|
||||
if (resolved.findIndex(r => r.mtis.length === 1 && r.mtis[0].fullyDottedRawName === name) < i) {
|
||||
probs.push(ParseProblem.Error(eit_types[1], `Repeated type: ${name}`));
|
||||
}
|
||||
}
|
||||
})
|
||||
break;
|
||||
}
|
||||
// check enclosed types
|
||||
owner_type.types.forEach(type => {
|
||||
resolveExtends(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} outername
|
||||
* @param {TypeDeclBlock} owner_type
|
||||
* @param {''|'.'|'$'} qualifier
|
||||
* @param {ResolvedImport[]} resolved_imports
|
||||
* @param {Map<string, import('../mti').Type>} typemap
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function resolveImplements(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
|
||||
if (!owner_type.implements_token) {
|
||||
return;
|
||||
}
|
||||
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
|
||||
if (!/class/.test(owner_type.kind())) {
|
||||
probs.push(ParseProblem.Error(owner_type.implements_token, `implements declaration is not valid for ${owner_type.kind()} type: ${fully_qualified_scope_name}`));
|
||||
return;
|
||||
}
|
||||
const eit_types = extractTypeList(owner_type.implements_token);
|
||||
// the scope for extends and implements needs to include any type variables, but not enclosed types
|
||||
const resolved = resolveTypes(eit_types.map(x => x.typename), fully_qualified_scope_name, resolved_imports, typemap);
|
||||
eit_types.forEach((eit_type,i) => {
|
||||
checkResolvedTypes(eit_type, resolved[i], probs);
|
||||
})
|
||||
eit_types.forEach((eit_type, i) => {
|
||||
const mti = resolved[i].mtis[0];
|
||||
if (resolved[i].mtis.length === 1 && mti.typeKind !== 'interface') {
|
||||
probs.push(ParseProblem.Error(eit_type, `Interface '${fully_qualified_scope_name}' cannot extend from ${mti.typeKind} type '${mti.fullyDottedRawName}'`));
|
||||
}
|
||||
// check for repeated types
|
||||
if (resolved[i].mtis.length === 1) {
|
||||
const name = resolved[i].mtis[0].fullyDottedRawName;
|
||||
if (resolved.findIndex(r => r.mtis.length === 1 && r.mtis[0].fullyDottedRawName === name) < i) {
|
||||
probs.push(ParseProblem.Error(eit_types[1], `Repeated type: ${name}`));
|
||||
}
|
||||
}
|
||||
})
|
||||
// check enclosed types
|
||||
owner_type.types.forEach(type => {
|
||||
resolveImplements(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} outername
|
||||
* @param {TypeDeclBlock} owner_type
|
||||
* @param {''|'.'|'$'} qualifier
|
||||
* @param {ResolvedImport[]} resolved_imports
|
||||
* @param {Map<string, import('../mti').Type>} typemap
|
||||
* @param {ParseProblem[]} probs
|
||||
*/
|
||||
function resolveMethodTypes(outername, owner_type, qualifier, resolved_imports, typemap, probs) {
|
||||
const method_type_names = [];
|
||||
owner_type.methods.forEach(m => {
|
||||
method_type_names.push(m.type);
|
||||
m.parameters.forEach(p => {
|
||||
method_type_names.push(p.type);
|
||||
});
|
||||
});
|
||||
const fully_qualified_scope_name = `${outername}${qualifier}${owner_type.simpleName}`;
|
||||
const resolved = resolveTypes(method_type_names, fully_qualified_scope_name, resolved_imports, typemap);
|
||||
let i = 0;
|
||||
owner_type.methods.forEach(method => {
|
||||
checkResolvedTypes(method, resolved[i++], probs);
|
||||
method.parameters.forEach((parameter, idx, arr) => {
|
||||
checkResolvedTypes(parameter, resolved[i++], probs);
|
||||
if (parameter.isVarArgs && idx !== arr.length-1) {
|
||||
probs.push(ParseProblem.Error(parameter, `Variable-arity parameters must be declared last.`));
|
||||
}
|
||||
});
|
||||
})
|
||||
// check enclosed types
|
||||
owner_type.types.forEach(type => {
|
||||
resolveMethodTypes(fully_qualified_scope_name, type, '$', resolved_imports, typemap, probs);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
*/
|
||||
module.exports = function(mod, imports) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
mod.types.forEach(type => {
|
||||
const qualifier = mod.packageName ? '.' : '';
|
||||
resolveExtends(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
|
||||
resolveImplements(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
|
||||
resolveFieldTypes(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
|
||||
resolveMethodTypes(mod.packageName, type, qualifier, imports.resolved, imports.typemap, probs);
|
||||
});
|
||||
|
||||
return probs;
|
||||
}
|
||||
@@ -3,13 +3,14 @@ const ParseProblem = require('../parsetypes/parse-problem');
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {{unresolved:*[]}} imports
|
||||
*/
|
||||
module.exports = function(mod, imports) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
imports.unresolved.forEach(i => {
|
||||
probs.push(ParseProblem.Warning(i, `Unresolved import: ${i.name}`));
|
||||
imports.unresolved.forEach(import_tokens => {
|
||||
probs.push(ParseProblem.Warning(import_tokens, `Unresolved import: ${import_tokens.name}`));
|
||||
})
|
||||
|
||||
return probs;
|
||||
|
||||
52
langserver/java/validation/unresolved-types.js
Normal file
52
langserver/java/validation/unresolved-types.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const { ModuleBlock, TypeDeclBlock } = require('../parser9');
|
||||
const ParseProblem = require('../parsetypes/parse-problem');
|
||||
const {SourceType} = require('../source-type');
|
||||
const {JavaType, CEIType, TypeArgument, UnresolvedType} = require('java-mti')
|
||||
|
||||
/**
|
||||
* @param {JavaType} type
|
||||
*/
|
||||
function checkType(type, typeTokens, probs) {
|
||||
if (type instanceof UnresolvedType) {
|
||||
probs.push(ParseProblem.Error(typeTokens, `Unknown type: ${type.label}`));
|
||||
return;
|
||||
}
|
||||
if (type instanceof CEIType) {
|
||||
type.typevars.forEach(tv => {
|
||||
if (tv instanceof TypeArgument) {
|
||||
checkType(tv.type, typeTokens, probs);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SourceType} type
|
||||
* @param {*} probs
|
||||
*/
|
||||
function checkUnresolvedTypes(type, probs) {
|
||||
type.extends_types.forEach(superclass => checkType(superclass.resolved, superclass.typeTokens, probs));
|
||||
type.implements_types.forEach(superintf => checkType(superintf.resolved, superintf.typeTokens, probs));
|
||||
type.fields.forEach(f => checkType(f.type, f._decl.typeTokens, probs));
|
||||
type.methods.forEach(m => {
|
||||
checkType(m.returnType, m._decl.typeTokens, probs);
|
||||
m.parameters.forEach(p => {
|
||||
checkType(p.type, p._decl.typeTokens, probs);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {ModuleBlock} mod
|
||||
* @param {*} imports
|
||||
* @param {SourceType[]} source_types
|
||||
*/
|
||||
module.exports = function(mod, imports, source_types) {
|
||||
/** @type {ParseProblem[]} */
|
||||
const probs = [];
|
||||
|
||||
source_types.forEach(type => checkUnresolvedTypes(type, probs));
|
||||
|
||||
return probs;
|
||||
}
|
||||
250
langserver/package-lock.json
generated
250
langserver/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langserver",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -10,13 +10,249 @@
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
|
||||
"dev": true
|
||||
},
|
||||
"jarscanner": {
|
||||
"version": "file:../../../nodejs/jarscanner",
|
||||
"java-mti": {
|
||||
"version": "file:../../../nodejs/java-mti",
|
||||
"requires": {
|
||||
"unzipper": "^0.10.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "13.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA=="
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
|
||||
},
|
||||
"binary": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
|
||||
"integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
|
||||
"requires": {
|
||||
"buffers": "~0.1.1",
|
||||
"chainsaw": "~0.1.0"
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
|
||||
"integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffer-indexof-polyfill": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz",
|
||||
"integrity": "sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8="
|
||||
},
|
||||
"buffers": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
|
||||
"integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
|
||||
},
|
||||
"chainsaw": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
|
||||
"integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
|
||||
"requires": {
|
||||
"traverse": ">=0.3.0 <0.4"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"duplexer2": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
||||
"integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
|
||||
"requires": {
|
||||
"readable-stream": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"fstream": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
|
||||
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"inherits": "~2.0.0",
|
||||
"mkdirp": ">=0.5 0",
|
||||
"rimraf": "2"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"listenercount": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
|
||||
"integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
"integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
|
||||
},
|
||||
"unzipper": {
|
||||
"version": "0.10.11",
|
||||
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz",
|
||||
"integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==",
|
||||
"requires": {
|
||||
"big-integer": "^1.6.17",
|
||||
"binary": "~0.3.0",
|
||||
"bluebird": "~3.4.1",
|
||||
"buffer-indexof-polyfill": "~1.0.0",
|
||||
"duplexer2": "~0.1.4",
|
||||
"fstream": "^1.0.12",
|
||||
"graceful-fs": "^4.2.2",
|
||||
"listenercount": "~1.0.1",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "~1.0.4"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "langserver",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Java language server for Android development",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"jarscanner": "file://~/dev/nodejs/jarscanner",
|
||||
"java-mti": "file:../../../nodejs/java-mti",
|
||||
"vscode-languageserver": "^6.1.1",
|
||||
"vscode-languageserver-textdocument": "^1.0.1"
|
||||
},
|
||||
|
||||
@@ -17,46 +17,17 @@ const {
|
||||
|
||||
const { TextDocument } = require('vscode-languageserver-textdocument');
|
||||
|
||||
const MTI = require('./java/mti');
|
||||
const { loadAndroidLibrary, JavaType } = require('java-mti');
|
||||
|
||||
const { ParseProblem } = require('./java/parser');
|
||||
const { parse, ModuleBlock } = require('./java/parser9');
|
||||
const { validate } = require('./java/validater');
|
||||
|
||||
/**
|
||||
* @typedef {Map<string, JavaType>} AndroidLibrary
|
||||
* @type {AndroidLibrary|Promise<AndroidLibrary>}
|
||||
*/
|
||||
let androidLibrary = null;
|
||||
function loadAndroidLibrary(retry) {
|
||||
try {
|
||||
androidLibrary = MTI.unpackJSON('/tmp/jarscanner/android-25/android-25.json');
|
||||
connection.console.log(`Android type cache loaded: ${androidLibrary.types.length} types from ${androidLibrary.packages.length} packages.`);
|
||||
} catch (e) {
|
||||
connection.console.log(`Failed to load android type cache`);
|
||||
if (retry) {
|
||||
return;
|
||||
}
|
||||
connection.console.log(`Rebuilding type cache...`);
|
||||
const jarscanner = require(`jarscanner/jarscanner`);
|
||||
fs.mkdir('/tmp/jarscanner', (err) => {
|
||||
if (err && err.errno !== -17) {
|
||||
connection.console.log(`Cannot create type cache folder. ${err.message}.`);
|
||||
return;
|
||||
}
|
||||
jarscanner.process_android_sdk_source(
|
||||
{
|
||||
destpath: '/tmp/jarscanner',
|
||||
sdkpath: process.env['ANDROID_SDK'],
|
||||
api: 25,
|
||||
cleandest: true,
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
connection.console.log(`Android cache build failed. ${err.message}.`);
|
||||
return;
|
||||
}
|
||||
loadAndroidLibrary(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
@@ -144,7 +115,7 @@ let documents = new TextDocuments({
|
||||
* @param {number} version
|
||||
*/
|
||||
update(document, changes, version) {
|
||||
connection.console.log(JSON.stringify({ what: 'update', changes, version }));
|
||||
connection.console.log(JSON.stringify({ what: 'update', /* changes, */ version }));
|
||||
//connection.console.log(`update ${version}`);
|
||||
//return document;
|
||||
if (parsed && document && parsed.uri === document.uri) {
|
||||
@@ -170,7 +141,14 @@ let hasWorkspaceFolderCapability = false;
|
||||
let hasDiagnosticRelatedInformationCapability = false;
|
||||
|
||||
connection.onInitialize((params) => {
|
||||
process.nextTick(loadAndroidLibrary);
|
||||
console.time('android-library-load')
|
||||
androidLibrary = loadAndroidLibrary('android-25').then(lib => {
|
||||
console.timeEnd('android-library-load')
|
||||
return androidLibrary = lib;
|
||||
}, err => {
|
||||
console.log(`android library load failed: ${err.message}`);
|
||||
return androidLibrary = new Map();
|
||||
});
|
||||
let capabilities = params.capabilities;
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
@@ -266,12 +244,20 @@ documents.onDidChangeContent((change) => {
|
||||
* @param {{uri}} textDocument
|
||||
*/
|
||||
async function validateTextDocument(textDocument) {
|
||||
if (androidLibrary instanceof Promise) {
|
||||
connection.console.log('Waiting for Android Library load');
|
||||
androidLibrary = await androidLibrary;
|
||||
}
|
||||
/** @type {ParseProblem[]} */
|
||||
let problems = [];
|
||||
connection.console.log('validateTextDocument');
|
||||
|
||||
if (parsed && parsed.result) {
|
||||
problems = validate(parsed.result, androidLibrary);
|
||||
try {
|
||||
problems = validate(parsed.result, androidLibrary);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
const diagnostics = problems
|
||||
@@ -353,10 +339,13 @@ connection.onCompletion(
|
||||
/**
|
||||
* @param {*} _textDocumentPosition TextDocumentPositionParams
|
||||
*/
|
||||
(_textDocumentPosition) => {
|
||||
async (_textDocumentPosition) => {
|
||||
// The pass parameter contains the position of the text document in
|
||||
// which code complete got requested. For the example we ignore this
|
||||
// info and always provide the same completion items.
|
||||
if (androidLibrary instanceof Promise) {
|
||||
androidLibrary = await androidLibrary;
|
||||
}
|
||||
const lib = androidLibrary;
|
||||
if (!lib) return [];
|
||||
const typeKindMap = {
|
||||
@@ -383,13 +372,13 @@ connection.onCompletion(
|
||||
kind: CompletionItemKind.Value,
|
||||
data: -1,
|
||||
})),
|
||||
...lib.types.map(
|
||||
...[...lib.values()].map(
|
||||
(t, idx) =>
|
||||
/** @type {CompletionItem} */
|
||||
({
|
||||
label: t.dottedRawName,
|
||||
label: t.dottedTypeName,
|
||||
kind: typeKindMap[t.typeKind],
|
||||
data: idx,
|
||||
data: t.shortSignature,
|
||||
})
|
||||
),
|
||||
])
|
||||
@@ -404,14 +393,17 @@ connection.onCompletionResolve(
|
||||
* @param {CompletionItem} item
|
||||
*/
|
||||
(item) => {
|
||||
const t = androidLibrary.types[item.data];
|
||||
if (androidLibrary instanceof Promise) {
|
||||
return item;
|
||||
}
|
||||
const t = androidLibrary.get(item.data);
|
||||
if (!t) {
|
||||
return item;
|
||||
}
|
||||
item.detail = `${t.package}.${t.dottedRawName}`;
|
||||
item.detail = t.fullyDottedRawName;
|
||||
item.documentation = t.docs && {
|
||||
kind: 'markdown',
|
||||
value: `${t.typeKind} **${t.dottedName}**\n\n${
|
||||
value: `${t.typeKind} **${t.dottedTypeName}**\n\n${
|
||||
t.docs
|
||||
.replace(/(<p ?.*?>)|(<\/?i>|<\/?em>)|(<\/?b>|<\/?strong>|<\/?dt>)|(<\/?tt>)|(<\/?code>|<\/?pre>)|(\{@link.+?\}|\{@code.+?\})|(<li>)|(<a href="\{@docRoot\}.*?">.+?<\/a>)|(<h\d>)|<\/?dd ?.*?>|<\/p ?.*?>|<\/h\d ?.*?>|<\/?div ?.*?>|<\/?[uo]l ?.*?>/gim, (_,p,i,b,tt,c,lc,li,a,h) => {
|
||||
return p ? '\n\n'
|
||||
|
||||
Reference in New Issue
Block a user