mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 09:59:25 +00:00
updated validation to use new JavaTypes module instead of MTIs
This commit is contained in:
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;
|
||||
}
|
||||
Reference in New Issue
Block a user