mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 18:08:29 +00:00
remove redundant types and functions used by old parser
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
* Each token also contains detailed state information used for completion suggestions.
|
* 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,
|
const { SourceType, SourceTypeIdent, SourceField, SourceMethod, SourceConstructor, SourceInitialiser, SourceParameter, SourceAnnotation,
|
||||||
SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType } = require('./source-types');
|
SourceUnit, SourcePackage, SourceImport, SourceArrayType, FixedLengthArrayType } = require('./source-types');
|
||||||
const ResolvedImport = require('./parsetypes/resolved-import');
|
const ResolvedImport = require('./parsetypes/resolved-import');
|
||||||
@@ -14,9 +14,9 @@ const { resolveTypeOrPackage, resolveNextTypeOrPackage } = require('./type-resol
|
|||||||
const { genericTypeArgs, typeIdent, typeIdentList } = require('./typeident');
|
const { genericTypeArgs, typeIdent, typeIdentList } = require('./typeident');
|
||||||
const { TokenList } = require("./TokenList");
|
const { TokenList } = require("./TokenList");
|
||||||
const { AnyMethod, AnyType, AnyValue } = require("./anys");
|
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 { resolveImports, resolveSingleImport } = require('./import-resolver');
|
||||||
const { checkAssignment, getTypeInheritanceList } = require('./expression-resolver');
|
const { getTypeInheritanceList } = require('./expression-resolver');
|
||||||
const { checkStatementBlock } = require('./statement-validater');
|
const { checkStatementBlock } = require('./statement-validater');
|
||||||
const { time, timeEnd } = require('../logging');
|
const { time, timeEnd } = require('../logging');
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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 <T extends Object>)
|
|
||||||
* These can typically appear in any order before a declaration
|
|
||||||
*
|
|
||||||
* @typedef {Token|Annotation|TypeParameters} Modifier
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = {}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -2,9 +2,6 @@ const ProblemSeverity = require('./problem-severity');
|
|||||||
const { TextBlock } = require('./textblock');
|
const { TextBlock } = require('./textblock');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./import')} ImportDeclaration
|
|
||||||
* @typedef {import('./modifier')} Modifier
|
|
||||||
* @typedef {import('./package')} PackageDeclaration
|
|
||||||
* @typedef {import('./problem-severity').Severity} Severity
|
* @typedef {import('./problem-severity').Severity} Severity
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {import('./token')} Token
|
|
||||||
* @typedef {import('./type')} TypeDeclaration
|
|
||||||
* @typedef {import('java-mti').JavaType} JavaType
|
* @typedef {import('java-mti').JavaType} JavaType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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<string,*>} 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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,261 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {Map<string,CEIType>} TypeMap
|
* @typedef {Map<string,CEIType>} 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 { 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 = {
|
module.exports = {
|
||||||
parse_type,
|
|
||||||
resolveType,
|
|
||||||
resolveTypes,
|
|
||||||
resolveTypeIdents,
|
|
||||||
ResolvedType,
|
|
||||||
resolveTypeOrPackage,
|
resolveTypeOrPackage,
|
||||||
resolveNextTypeOrPackage,
|
resolveNextTypeOrPackage,
|
||||||
resolveNextPackage,
|
resolveNextPackage,
|
||||||
|
|||||||
Reference in New Issue
Block a user