remove redundant types and functions used by old parser

This commit is contained in:
Dave Holoway
2020-06-29 16:15:24 +01:00
parent 9498888ee6
commit 31f3826971
17 changed files with 4 additions and 886 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 = {}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -1,6 +1,4 @@
/**
* @typedef {import('./token')} Token
* @typedef {import('./type')} TypeDeclaration
* @typedef {import('java-mti').JavaType} JavaType
*/

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;