diff --git a/langserver/java/body-parser.js b/langserver/java/body-parser.js index a8a513e..333ee72 100644 --- a/langserver/java/body-parser.js +++ b/langserver/java/body-parser.js @@ -4,7 +4,7 @@ * * Each token also contains detailed state information used for completion suggestions. */ -const { JavaType, CEIType, PrimitiveType, ArrayType, UnresolvedType, TypeVariable, Field, Method } = require('java-mti'); +const { CEIType, PrimitiveType, ArrayType, UnresolvedType, TypeVariable, Field, Method } = require('java-mti'); const { SourceType, SourceTypeIdent, SourceField, SourceMethod, SourceConstructor, SourceInitialiser, SourceParameter, SourceAnnotation, SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType } = require('./source-types'); const ResolvedImport = require('./parsetypes/resolved-import'); @@ -14,9 +14,9 @@ const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resol const { genericTypeArgs, typeIdent, typeIdentList } = require('./typeident'); const { TokenList } = require("./TokenList"); const { AnyMethod, AnyType, AnyValue } = require("./anys"); -const { Label, Local, MethodDeclarations, ResolvedIdent, ResolveInfo } = require("./body-types"); +const { Label, Local, MethodDeclarations, ResolvedIdent } = require("./body-types"); const { resolveImports, resolveSingleImport } = require('./import-resolver'); -const { checkAssignment, getTypeInheritanceList } = require('./expression-resolver'); +const { getTypeInheritanceList } = require('./expression-resolver'); const { checkStatementBlock } = require('./statement-validater'); const { time, timeEnd } = require('../logging'); diff --git a/langserver/java/parsetypes/annotation.js b/langserver/java/parsetypes/annotation.js deleted file mode 100644 index 5087c2d..0000000 --- a/langserver/java/parsetypes/annotation.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @typedef {import('./token')} Token - */ - - class Annotation { - /** - * @param {Token} at - * @param {Token} name - */ - constructor(at, name) { - this.at = at; - this.name = name; - } -} - -module.exports = Annotation; diff --git a/langserver/java/parsetypes/declaration.js b/langserver/java/parsetypes/declaration.js deleted file mode 100644 index 0ad50ae..0000000 --- a/langserver/java/parsetypes/declaration.js +++ /dev/null @@ -1,49 +0,0 @@ -const Token = require('./token'); -/** - * @typedef {import('./modifier')} Modifier - * @typedef {import('./type')} TypeDeclaration - */ - -/** - * Base class for Java declarations. - */ -class Declaration { - /** - * @param {TypeDeclaration} owner_type the type this declaration belongs to (if any) - * @param {Token} docs JavaDocs associated with the declaration - * @param {Modifier[]} modifiers annotations, modifier keywords and type parameters - */ - constructor(owner_type, docs, modifiers) { - this.owner_type = owner_type; - this.docs = docs; - this.modifiers = modifiers; - } - - /** - * returns the raw JavaDoc string or an empty string if no doc is present - */ - getDocString() { - return this.docs ? this.docs.text : ''; - } - - /** - * Returns the raw access modifier text values - * @returns {string[]} - */ - getAccessModifierValues() { - // @ts-ignore - return this.modifiers.filter(m => m instanceof Token).map(t => t.text); - } - - /** - * Finds the token matching the specified modifier - * @param {string} name - * @returns {Token} - */ - findModifier(name) { - // @ts-ignore - return this.modifiers.find(m => (m instanceof Token) && (m.text === name)); - } -} - -module.exports = Declaration; diff --git a/langserver/java/parsetypes/fmc.js b/langserver/java/parsetypes/fmc.js deleted file mode 100644 index f977e3f..0000000 --- a/langserver/java/parsetypes/fmc.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @typedef {import('./modifier')} Modifier - * @typedef {import('./parameter')} ParameterDeclaration - * @typedef {import('./token')} Token - * @typedef {import('./type')} TypeDeclaration - * @typedef {import('./typeident')} TypeIdent - */ -const Declaration = require('./declaration'); -const ParseProblem = require('./parse-problem'); -const ProblemSeverity = require('./problem-severity'); - -/** - * Field, method or constructor declaration - */ -class FMCDeclaration extends Declaration { - /** - * - * @param {TypeDeclaration} owner_type - * @param {Token} docs - * @param {Modifier[]} modifiers - * @param {'field'|'method'|'constructor'} kind - * @param {Token} name - * @param {TypeIdent} type - * @param {Token} equals_comma_sc - * @param {ParameterDeclaration[]} parameters - */ - constructor(owner_type, docs, modifiers, kind, name, type, equals_comma_sc, parameters) { - super(owner_type, docs, modifiers); - this.kind = kind; - this.name = name; - this.type = type; - this.equals_comma_sc = equals_comma_sc; - this.parameters = parameters || []; - } - - validate() { - const checkDuplicateParameterNames = () => { - const done = new Set(); - return this.parameters - .filter(p => { - if (done.has(p.name.text)) { - return true; - } - done.add(p.name.text); - }) - .map(p => - new ParseProblem(p.name, `Duplicate parameter name: '${p.name.text}'`, ProblemSeverity.Error) - ); - }; - const checkParameterCommas = () => { - const last_param_idx = this.parameters.length - 1; - return this.parameters.map((p, idx) => { - if ((idx < last_param_idx) && !p.comma) { - return new ParseProblem(p.lastToken(), 'Missing comma', ProblemSeverity.Error); - } - else if ((idx === last_param_idx) && p.comma) { - return ParseProblem.syntaxError(p.comma); - } - }); - } - const checkFieldSemicolon = () => { - if (this.kind === 'field') { - if (!this.equals_comma_sc) { - return new ParseProblem(this.name, `Missing operator or semicolon`, ProblemSeverity.Error); - } - } - return null; - } - const checkVarargsIsLastParameter = () => { - return this.parameters - .slice(0, -1) - .filter(p => p.varargs) - .map(p => - new ParseProblem(p.varargs, 'A variable arity parameter must be declared last', ProblemSeverity.Error) - ); - }; - const problems = [ - ...ParseProblem.checkAccessModifiers(this.modifiers, this.kind), - ...ParseProblem.checkDuplicateModifiers(this.modifiers), - ...ParseProblem.checkConflictingModifiers(this.modifiers), - ...checkParameterCommas(), - ...checkDuplicateParameterNames(), - ...checkVarargsIsLastParameter(), - checkFieldSemicolon(), - ]; - return problems; - } -} - -module.exports = FMCDeclaration; diff --git a/langserver/java/parsetypes/import.js b/langserver/java/parsetypes/import.js deleted file mode 100644 index 6e3d313..0000000 --- a/langserver/java/parsetypes/import.js +++ /dev/null @@ -1,37 +0,0 @@ -const Declaration = require('./declaration'); -const Token = require('./token'); - -/** - * @typedef {import('./modifier')} Modifier - */ - -class ImportDeclaration extends Declaration { - /** - * @param {Token} docs - * @param {Modifier[]} modifiers - * @param {Token[]} nameparts - * @param {Token} static_ - * @param {Token} asterisk - * @param {Token} semicolon - */ - constructor(docs, modifiers, nameparts, static_, asterisk, semicolon) { - super(null, docs, modifiers); - this.nameparts = nameparts; - this.static_ = static_; - this.asterisk = asterisk; - this.semicolon = semicolon; - } - - /** - * Returns the dotted portion of the import declaration (excluding any demand-load part) - */ - getDottedName() { - return this.nameparts.map(x => x.text).join('.'); - } - - lastToken() { - return this.semicolon || this.asterisk || this.nameparts.slice(-1)[0]; - } -} - -module.exports = ImportDeclaration; diff --git a/langserver/java/parsetypes/modifier.js b/langserver/java/parsetypes/modifier.js deleted file mode 100644 index f11b850..0000000 --- a/langserver/java/parsetypes/modifier.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @typedef {import('./annotation')} Annotation - * @typedef {import('./type-parameters')} TypeParameters - * @typedef {import('./token')} Token - * - * Each Modifier is one of - * - a token representing a modifier keyword (e.g public, static, etc) - * - an Annotation (eg. @Override) - * - or a TypeParameters section (eg ) - * These can typically appear in any order before a declaration - * - * @typedef {Token|Annotation|TypeParameters} Modifier - */ - - module.exports = {} diff --git a/langserver/java/parsetypes/package.js b/langserver/java/parsetypes/package.js deleted file mode 100644 index 126802a..0000000 --- a/langserver/java/parsetypes/package.js +++ /dev/null @@ -1,39 +0,0 @@ -const Declaration = require('./declaration'); -const ParseProblem = require('./parse-problem'); -/** - * @typedef {import('./modifier')} Modifier - * @typedef {import('./token')} Token - */ - -class PackageDeclaration extends Declaration { - /** - * @param {Token} docs - * @param {Modifier[]} modifiers - * @param {Token[]} nameparts - * @param {Token} semicolon - */ - constructor(docs, modifiers, nameparts, semicolon) { - super(null, docs, modifiers); - this.nameparts = nameparts; - this.semicolon = semicolon; - } - - dottedName() { - return this.nameparts.map(t => t.text).join('.'); - } - - lastToken() { - return this.semicolon || this.nameparts.slice(-1)[0]; - } - - validate() { - /** @type {ParseProblem[]} */ - const problems = [ - ParseProblem.checkSemicolon(this), - ...ParseProblem.checkNonKeywordIdents(this.nameparts), - ]; - return problems; - } -} - -module.exports = PackageDeclaration; diff --git a/langserver/java/parsetypes/parameter.js b/langserver/java/parsetypes/parameter.js deleted file mode 100644 index 6cb911d..0000000 --- a/langserver/java/parsetypes/parameter.js +++ /dev/null @@ -1,33 +0,0 @@ -const Declaration = require('./declaration'); - -/** - * @typedef {import('./modifier')} Modifier - * @typedef {import('./typeident')} TypeIdent - * @typedef {import('./token')} Token - */ - - /** - * A single parameter declaration - */ - class ParameterDeclaration extends Declaration { - /** - * @param {Modifier[]} modifiers - * @param {TypeIdent} type - * @param {Token} varargs - * @param {Token} name - * @param {Token} comma - */ - constructor(modifiers, type, varargs, name, comma) { - super(null, null, modifiers); - this.name = name; - this.type = type; - this.varargs = varargs; - this.comma = comma; - } - - lastToken() { - return this.comma || this.name; - } -} - -module.exports = ParameterDeclaration; diff --git a/langserver/java/parsetypes/parse-error.js b/langserver/java/parsetypes/parse-error.js deleted file mode 100644 index 892511e..0000000 --- a/langserver/java/parsetypes/parse-error.js +++ /dev/null @@ -1,29 +0,0 @@ -const Declaration = require('./declaration'); -const ParseProblem = require('./parse-problem'); -/** - * @typedef {import('./modifier')} Modifier - * @typedef {import('./token')} Token - */ - -class ParseSyntaxError extends Declaration { - /** - * @param {Token} docs - * @param {Modifier[]} modifiers - * @param {Token} errorToken - */ - constructor(docs, modifiers, errorToken) { - super(null, docs, modifiers); - this.errorToken = errorToken; - } - - validate() { - if (!this.errorToken) { - return []; - } - return [ - ParseProblem.syntaxError(this.errorToken), - ] - } -} - -module.exports = ParseSyntaxError; diff --git a/langserver/java/parsetypes/parse-problem.js b/langserver/java/parsetypes/parse-problem.js index 0602248..3bb7c11 100644 --- a/langserver/java/parsetypes/parse-problem.js +++ b/langserver/java/parsetypes/parse-problem.js @@ -2,9 +2,6 @@ const ProblemSeverity = require('./problem-severity'); const { TextBlock } = require('./textblock'); /** - * @typedef {import('./import')} ImportDeclaration - * @typedef {import('./modifier')} Modifier - * @typedef {import('./package')} PackageDeclaration * @typedef {import('./problem-severity').Severity} Severity */ diff --git a/langserver/java/parsetypes/parse-result.js b/langserver/java/parsetypes/parse-result.js deleted file mode 100644 index d3ad84a..0000000 --- a/langserver/java/parsetypes/parse-result.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @typedef {import('./import')} ImportDeclaration - * @typedef {import('./package')} PackageDeclaration - * @typedef {import('./parse-error')} ParseSyntaxError - * @typedef {import('./type')} TypeDeclaration - */ - - class ParseResult { - /** - * - * @param {PackageDeclaration} package_decl - * @param {ImportDeclaration[]} imports - * @param {TypeDeclaration[]} types - * @param {ParseSyntaxError[]} invalids - */ - constructor(package_decl, imports, types, invalids) { - this.package = package_decl; - this.imports = imports; - this.types = types; - this.invalids = invalids; - } -} - -module.exports = ParseResult; diff --git a/langserver/java/parsetypes/resolved-type.js b/langserver/java/parsetypes/resolved-type.js index 12ecbb1..0413831 100644 --- a/langserver/java/parsetypes/resolved-type.js +++ b/langserver/java/parsetypes/resolved-type.js @@ -1,6 +1,4 @@ /** - * @typedef {import('./token')} Token - * @typedef {import('./type')} TypeDeclaration * @typedef {import('java-mti').JavaType} JavaType */ diff --git a/langserver/java/parsetypes/token.js b/langserver/java/parsetypes/token.js deleted file mode 100644 index e3d3c9c..0000000 --- a/langserver/java/parsetypes/token.js +++ /dev/null @@ -1,17 +0,0 @@ -class Token { - /** - * - * @param {number} source_idx - * @param {string} text - * @param {string} simplified_text - * @param {number} simplified_text_idx - */ - constructor(source_idx, text, simplified_text, simplified_text_idx) { - this.source_idx = source_idx; - this.text = text; - this.simplified_text = simplified_text; - this.simplified_text_idx = simplified_text_idx; - } -} - -module.exports = Token; diff --git a/langserver/java/parsetypes/type-parameters.js b/langserver/java/parsetypes/type-parameters.js deleted file mode 100644 index c558923..0000000 --- a/langserver/java/parsetypes/type-parameters.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @typedef {import('./token')} Token - */ - -class TypeParameters { - /** - * - * @param {Token} open - * @param {Token} close - */ - constructor(open, close) { - this.open = open; - this.close = close; - } -} - -module.exports = TypeParameters; diff --git a/langserver/java/parsetypes/type.js b/langserver/java/parsetypes/type.js deleted file mode 100644 index d24fba2..0000000 --- a/langserver/java/parsetypes/type.js +++ /dev/null @@ -1,229 +0,0 @@ -const Declaration = require('./declaration'); -const ParseProblem = require('./parse-problem'); -const ProblemSeverity = require('./problem-severity'); -const ResolvedImport = require('../import-resolver').ResolvedImport; -const { resolveTypeIdents } = require('../type-resolver'); -const Token = require('./token'); - -/** - * @typedef {import('./import')} ImportDeclaration - * @typedef {import('./fmc')} FMCDeclaration - * @typedef {import('./modifier')} Modifier - * @typedef {import('./parameter')} ParameterDeclaration - * @typedef {import('./typeident')} TypeIdent - */ - -/** - * Represents a single Java type (class, interface, enum or @-interface) declaration - */ -class TypeDeclaration extends Declaration { - /** - * - * @param {TypeDeclaration} owner_type - * @param {Token} docs - * @param {Modifier[]} modifiers - * @param {'class'|'interface'|'enum'|'@interface'} kind - * @param {Token} name - */ - constructor(owner_type, docs, modifiers, kind, name) { - super(owner_type, docs, modifiers); - this.kind = kind; - this.name = name; - /** @type {FMCDeclaration[]} */ - this.declarations = []; - /** @type {{decl_kw:Token, typelist:TypeIdent[]}[]} */ - this.super_declarations = []; - } - - /** - * returns the $-qualified name of this type (excluding package) - */ - qualifiedName() { - if (!this.owner_type) { - // top-level type - return this.name.text; - } - const parts = []; - for (let t = this; t;) { - parts.unshift(t.name.text); - // @ts-ignore - t = t.owner_type; - } - return parts.join('$'); - } - - qualifiedDottedName() { - return this.qualifiedName().replace(/[$]/g, '.'); - } - - validate() { - const checkSuperDeclarations = () => { - const res = { - extends: [], - implements: [], - first: this.super_declarations[0], - }; - const problems = []; - this.super_declarations.forEach((sd) => res[sd.decl_kw.text].push(sd)); - for (let i = 1; i < res.extends.length; i++) { - problems.push(new ParseProblem(res.extends[i].decl_kw, `Types cannot have multiple 'extends' declarations`, ProblemSeverity.Error)); - } - for (let i = 1; i < res.implements.length; i++) { - problems.push(new ParseProblem(res.extends[i].decl_kw, `Types cannot have multiple 'implements' declarations`, ProblemSeverity.Error)); - } - if (res.extends.length > 0 && res.implements.length > 0 && res.first.decl_kw.text !== 'extends') { - problems.push(new ParseProblem(res.extends[0].decl_kw, `'extends' declaration must appear before 'implements'`, ProblemSeverity.Error)); - } - if (this.kind === 'class' && res.extends.length === 1 && res.extends[0].typelist.length > 1) { - problems.push(new ParseProblem(res.extends[0].decl_kw, `Class types cannot extend from multiple super types`, ProblemSeverity.Error)); - } - return problems; - }; - const checkDuplicateFieldNames = () => { - // get list of fields, sorted by name - const fields = this.declarations - .filter((d) => d.kind === 'field') - .slice() - .sort((a, b) => a.name.text.localeCompare(b.name.text)); - const probs = []; - let name = ''; - fields.forEach((decl, idx, arr) => { - const next = arr[idx + 1]; - if ((next && decl.name.text === next.name.text) || decl.name.text === name) { - probs.push(new ParseProblem(decl.name, `Duplicate field name: '${decl.name.text}'`, ProblemSeverity.Error)); - } - name = decl.name.text; - }); - return probs; - }; - let problems = [ - ...ParseProblem.checkDuplicateModifiers(this.modifiers), - ...ParseProblem.checkConflictingModifiers(this.modifiers), - ...ParseProblem.checkAccessModifiers(this.modifiers, this.kind), - ...ParseProblem.checkNonKeywordIdents([this.name]), - ...ParseProblem.checkNonKeywordIdents(this.declarations.map((d) => d.name)), - ...checkDuplicateFieldNames(), - ...checkSuperDeclarations(), - ...this.declarations.reduce((probs, d) => { - return [...probs, ...d.validate()]; - }, []), - ]; - return problems; - } - - /** - * @param {string} package_name - * @param {ResolvedImport[]} imports - * @param {Map} typemap - */ - validateTypes(package_name, imports, typemap) { - const problems = []; - const fqtypename = package_name ? `${package_name}.${this.qualifiedName()}` : this.qualifiedName(); - - /** @type {TypeIdent[]} */ - let typeidents = []; - - // check extends - this.super_declarations.filter(sd => sd.decl_kw.text === 'extends').forEach(sd => { - sd.typelist.forEach(typeident => typeidents.push(typeident)); - }) - const resolved_extends = resolveTypeIdents(typeidents, package_name, imports, typemap); - resolved_extends.forEach((rt,i) => { - checkResolvedType(rt, typeidents[i]); - if (this.kind === 'class' && rt.mtis.length === 1) { - // class extend type must be a class - if (rt.mtis[0].typeKind !== 'class') { - problems.push(new ParseProblem(typeidents[i].tokens, `Class '${this.name.text}' cannot extend from ${rt.mtis[0].typeKind} '${rt.label}'; the specified type must be a non-final class.`, ProblemSeverity.Error)); - } - // class extend type cannot be final - else if (rt.mtis[0].hasModifier('final')) { - problems.push(new ParseProblem(typeidents[i].tokens, `Class '${this.name.text}' cannot extend from final class '${rt.mtis[0].fullyDottedRawName}'.`, ProblemSeverity.Error)); - } - } - }); - - // check implements - typeidents = []; - this.super_declarations.filter(sd => sd.decl_kw.text === 'implements').forEach(sd => { - sd.typelist.forEach(typeident => typeidents.push(typeident)); - if (this.kind !== 'class' && this.kind !== 'enum') { - problems.push(new ParseProblem(sd.decl_kw, `implements declarations are not permitted for ${this.kind} types`, ProblemSeverity.Error)); - } - }) - const resolved_implements = resolveTypeIdents(typeidents, package_name, imports, typemap); - resolved_implements.forEach((rt,i) => { - checkResolvedType(rt, typeidents[i]); - if (/class|enum/.test(this.kind) && rt.mtis.length === 1) { - // class implements types must be interfaces - if (rt.mtis[0].typeKind !== 'interface') { - problems.push(new ParseProblem(typeidents[i].tokens, `Type '${this.name.text}' cannot implement ${rt.mtis[0].typeKind} type '${rt.mtis[0].fullyDottedRawName}'; the specified type must be an interface.`, ProblemSeverity.Error)); - } - else if (!this.findModifier('abstract')) { - // if the class is not abstract, it must implement all the methods in the interface - // - we can't check this until the MTI for the class is complete - const unimplemented_methods = rt.mtis[0].methods.filter(m => true); - unimplemented_methods.forEach(method => { - problems.push(new ParseProblem(typeidents[i].tokens, `Type '${this.name.text}' is not abstract and does not implement method '${method.toDeclSource()}' declared in interface '${rt.mtis[0].fullyDottedRawName}'.`, ProblemSeverity.Error)); - }) - } - } - }); - - // check field, method-return and parameter types - typeidents = []; - this.declarations.forEach((d) => { - if (d.kind !== 'constructor') { - typeidents.push(d.type); - } - if (d.parameters) { - d.parameters.forEach((p) => { - typeidents.push(p.type); - }); - } - }); - - const resolved_types = resolveTypeIdents(typeidents, fqtypename, imports, typemap); - // warn about missing and ambiguous types - function checkResolvedType(rt, typeident) { - if (rt.error) { - problems.push(new ParseProblem(typeident.tokens, rt.error, ProblemSeverity.Error)); - return; - } - if (rt.mtis.length === 0) { - problems.push(new ParseProblem(typeident.tokens, `Type not found: ${rt.label}`, ProblemSeverity.Error)); - return; - } - if (rt.mtis.length > 1) { - const names = rt.mtis.map(mti => mti.fullyDottedRawName).join(`' or '`); - problems.push(new ParseProblem(typeident.tokens, `Ambiguous type: ${rt.label} - could be '${names}'.`, ProblemSeverity.Error)); - return; - } - rt.mtis.forEach(mti => { - // void arrays are illegal - if (mti.name.startsWith('void[')) { - problems.push(new ParseProblem(typeident.tokens, `primitive void arrays are not a valid type.`, ProblemSeverity.Error)); - } - }) - } - resolved_types.forEach((rt,i) => { - checkResolvedType(rt, typeidents[i]); - - // check any type arguments - rt.parts.filter(p => p.typeargs).forEach(p => { - p.typeargs.forEach(typearg => { - checkResolvedType(typearg, typeidents[i]); - // check type arguments are not primitives (primitive arrays are ok) - if (typearg.mtis.length === 1) { - if (typearg.mtis[0].typeKind === 'primitive') { - problems.push(new ParseProblem(typeidents[i].tokens, `Type arguments cannot be primitive types.`, ProblemSeverity.Error)); - } - } - }) - }); - - }); - return problems; - } -} - -module.exports = TypeDeclaration; diff --git a/langserver/java/parsetypes/typeident.js b/langserver/java/parsetypes/typeident.js deleted file mode 100644 index a24a6de..0000000 --- a/langserver/java/parsetypes/typeident.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @typedef {import('./token')} Token - * @typedef {import('./resolved-type')} ResolvedType - */ - -/** - * Class to represent a declared type in methods, fields, parameters and variables - */ -class TypeIdent { - /** - * @param {Token[]} tokens - */ - constructor(tokens) { - this.tokens = tokens; - /** @type {ResolvedType} */ - this.resolved = null; - } - - lastToken() { - return this.tokens[this.tokens.length - 1]; - } -} - -module.exports = TypeIdent; diff --git a/langserver/java/type-resolver.js b/langserver/java/type-resolver.js index bc33568..311538f 100644 --- a/langserver/java/type-resolver.js +++ b/langserver/java/type-resolver.js @@ -1,261 +1,8 @@ /** * @typedef {Map} TypeMap */ -const { JavaType, PrimitiveType, ArrayType, CEIType, MethodBase, TypeVariable } = require('java-mti'); +const { JavaType, CEIType, MethodBase, TypeVariable } = require('java-mti'); const { ResolvedImport } = require('./import-resolver'); -const ResolvedType = require('./parsetypes/resolved-type'); - -/** - * Parse a type into its various components - * @param {string} label - * @returns {{type:ResolvedType, error:string}} - */ -function parse_type(label) { - const type = new ResolvedType(); - let re = /([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)|(\.[a-zA-Z_]\w*)|[<,>]|((?:\[\])+)|( +)|./g; - let parts = [type.addTypePart()]; - for (let m; m = re.exec(label);) { - if (m[4]) { - // ignore ws - continue; - } - if (!parts[0].name) { - if (m[1]) { - parts[0].name = m[1]; - continue; - } - return { type, error: 'Missing type identifier' }; - } - if (m[0] === '<') { - if (!parts[0].typeargs && !parts[0].owner.arrdims) { - // start of type arguments - start a new type - const t = new ResolvedType(true); - parts[0].typeargs = [t]; - parts.unshift(t.addTypePart()); - continue; - } - return { type, error: `Unexpected '<' character` }; - } - if (m[0] === ',') { - if (parts[1] && parts[1].typeargs) { - // type argument separator - replace the type on the stack - const t = new ResolvedType(true); - parts[1].typeargs.push(t); - parts[0] = t.addTypePart(); - continue; - } - return { type, error: `Unexpected ',' character` }; - } - if (m[0] === '>') { - if (parts[1] && parts[1].typeargs) { - // end of type arguments - parts.shift(); - continue; - } - return { type, error: `Unexpected '>' character` }; - } - if (m[2]) { - if (parts[0].typeargs || parts[0].outer) { - // post-type-args enclosed type - parts[0] = parts[0].inner = parts[0].owner.addTypePart(m[2].slice(1), parts[0]); - continue; - } - return { type, error: `Unexpected '.' character` }; - } - if (m[3]) { - parts[0].owner.arrdims = m[3].length / 2; - continue; - } - return { type, error: `Invalid type` }; - } - - if (parts.length !== 1) { - // one or more missing > - return { type, error: `Missing >` }; - } - - return { type, error: '' }; -} - - -/** - * Construct a regex to search for an enclosed type in the current and outer scopes of a given type - * - * @param {string} fully_qualified_scope the JRE name (a.b.X$Y) of the current type scope - * @param {string} dotted_raw_typename the dotted name of the type we are searching for - */ -function createTypeScopeRegex(fully_qualified_scope, dotted_raw_typename) { - // split the type name across enclosed type boundaries - const scopes = fully_qualified_scope.split('$'); - - // 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,'[$]'); - - // bulld up the list of possible type matches based upon each outer scope of the type - const enclosed_type_regexes = []; - while (scopes.length) { - enclosed_type_regexes.push(`${scopes.join('[$]')}[$]${enclosed_raw_typename}`); - scopes.pop(); - } - // the final regex is an exact match of possible type names, sorted from inner scope to outer (top-level) scope - return new RegExp(`^(${enclosed_type_regexes.join('|')})$`); -} - -/** - * 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 {TypeMap} typemap - */ -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 JavaTypes for the type and type arguments - resolveCompleteType(type, fully_qualified_scope, resolved_imports, typemap); - return type; -} - -/** - * - * @param {ResolvedType} type - * @param {string} fully_qualified_scope - * @param {ResolvedImport[]} resolved_imports - * @param {TypeMap} typemap - */ -function resolveCompleteType(type, 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 => { - p.typeargs.forEach(typearg => { - resolveCompleteType(typearg, fully_qualified_scope, resolved_imports, typemap); - }) - }) -} - - -/** - * @param {string} dotted_raw_typename - * @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 {TypeMap} 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 types - const array_types = types.map(t => new ArrayType(t, arraydims)); - return array_types; - } - - return types; -} - -/** - * 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 {TypeMap} typemap - * @param {ResolvedImport[]} resolved_imports The list of types resolved from the imports - * @returns {(PrimitiveType|CEIType)[]} - */ -function findRawJavaTypes(dotted_raw_typename, fully_qualified_scope, resolved_imports, typemap) { - - // first check if it's a simple primitive - if (PrimitiveType.isPrimitiveTypeName(dotted_raw_typename)) { - // return the primitive type - return [PrimitiveType.fromName(dotted_raw_typename)]; - } - - // create a regex to search for the type name - // - the first search is for exact type matches inside the current type scope (and any parent type scopes) - let search = createTypeScopeRegex(fully_qualified_scope, dotted_raw_typename); - let matched_types = - resolved_imports.map(ri => ({ - ri, - mtis: ri.fullyQualifiedNames.filter(fqn => search.test(fqn)).map(fqn => ri.types.get(fqn)) - })) - .filter(x => x.mtis.length); - - if (!matched_types.length) { - // 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 the imports for the type - matched_types = - resolved_imports.map(ri => ({ - ri, - mtis: ri.fullyQualifiedNames.filter(fqn => search.test(fqn)).map(fqn => ri.types.get(fqn)) - })) - .filter(x => x.mtis.length); - } - - // if the type matches multiple import entries, exact imports take prioirity over demand-load imports - let exact_import_matches = matched_types.filter(x => x.ri.import && !x.ri.import.isDemandLoad); - if (exact_import_matches.length) { - if (exact_import_matches.length < matched_types.length) { - matched_types = exact_import_matches; - } - } - - 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,'[/$]')}$`); - for (let typename of typemap.keys()) { - if (search.test(typename)) { - matched_types = [{ - ri: null, - mtis: [typemap.get(typename)] - }]; - break; - } - } - } - - // at this point, we should (hopefully) have a single matched type - // - 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((types, type) => [...types, ...type.mtis] , []) -} - -/** - * Converts an array of type name strings to resolved types - * @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 {TypeMap} typemap - */ -function resolveTypes(types, fully_qualified_scope, resolved_imports, typemap) { - return types.map(typename => resolveType(typename, fully_qualified_scope, resolved_imports, typemap)); -} - -/** - * Converts an array of TypeIdent instances to resolved types - * @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 {TypeMap} typemap - */ -function resolveTypeIdents(types, fully_qualified_scope, resolved_imports, typemap) { - const names = types.map(typeident => - typeident.tokens.map(token => token.text).join('') - ); - return resolveTypes(names, fully_qualified_scope, resolved_imports, typemap); -} - /** * @@ -399,11 +146,6 @@ function resolveNextPackage(package_name, ident, typemap) { } module.exports = { - parse_type, - resolveType, - resolveTypes, - resolveTypeIdents, - ResolvedType, resolveTypeOrPackage, resolveNextTypeOrPackage, resolveNextPackage,