mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-22 17:39:19 +00:00
189 lines
6.7 KiB
JavaScript
189 lines
6.7 KiB
JavaScript
const { SourceType, SourceMethod, SourceParameter, SourceField, SourceConstructor, SourceInitialiser } = require('../source-types');
|
|
const { Token } = require('../tokenizer');
|
|
const ParseProblem = require('../parsetypes/parse-problem');
|
|
|
|
/**
|
|
* @param {Token[]} mods
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkDuplicate(mods, probs) {
|
|
if (mods.length <= 1) {
|
|
return;
|
|
}
|
|
const m = new Map();
|
|
for (let mod of mods) {
|
|
const firstmod = m.get(mod.source);
|
|
if (firstmod === undefined) {
|
|
m.set(mod.source, mod);
|
|
} else {
|
|
probs.push(ParseProblem.Error(mod, 'Duplicate modifier'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Token[]} mods
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkConflictingAccess(mods, probs) {
|
|
if (mods.length <= 1) {
|
|
return;
|
|
}
|
|
const allmods = mods.map(m => m.source).join(' ');
|
|
for (let mod of mods) {
|
|
let match;
|
|
switch (mod.source) {
|
|
case 'private':
|
|
match = allmods.match(/protected|public/);
|
|
break;
|
|
case 'protected':
|
|
match = allmods.match(/private|public/);
|
|
break;
|
|
case 'public':
|
|
match = allmods.match(/private|protected/);
|
|
break;
|
|
}
|
|
if (match) {
|
|
probs.push(ParseProblem.Error(mod, `Access modifier '${mod.source}' conflicts with '${match[0]}'`));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {SourceField} field
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkFieldModifiers(field, probs) {
|
|
checkDuplicate(field.modifierTokens, probs);
|
|
checkConflictingAccess(field.modifierTokens, probs);
|
|
for (let mod of field.modifierTokens) {
|
|
switch (mod.source) {
|
|
case 'abstract':
|
|
probs.push(ParseProblem.Error(mod, 'Field declarations cannot be abstract'));
|
|
break;
|
|
case 'native':
|
|
probs.push(ParseProblem.Error(mod, 'Field declarations cannot be native'));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {SourceParameter} param
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkParameterModifiers(param, probs) {
|
|
// the only permitted modifier is final
|
|
let has_final = false;
|
|
param.modifierTokens.forEach(mod => {
|
|
if (mod.value === 'final') {
|
|
if (has_final) {
|
|
probs.push(ParseProblem.Error(mod, `Repeated modifier: final`));
|
|
}
|
|
has_final = true;
|
|
return;
|
|
}
|
|
probs.push(ParseProblem.Error(mod, `Parameter declarations cannot be ${mod.value}`));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {SourceType} type
|
|
* @param {Map<string,*>} ownertypemods
|
|
* @param {SourceMethod} method
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkMethodModifiers(type, ownertypemods, method, probs) {
|
|
checkDuplicate(method.modifierTokens, probs);
|
|
checkConflictingAccess(method.modifierTokens, probs);
|
|
|
|
method.parameters.forEach(p => checkParameterModifiers(p, probs));
|
|
|
|
const allmods = new Map(method.modifierTokens.map(m => [m.source, m]));
|
|
const is_interface_kind = /@?interface/.test(type.typeKind);
|
|
const has_body = method.hasImplementation;
|
|
|
|
if (allmods.has('abstract') && allmods.has('final')) {
|
|
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations cannot be abstract and final'));
|
|
}
|
|
if (allmods.has('abstract') && allmods.has('native')) {
|
|
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations cannot be abstract and native'));
|
|
}
|
|
if (allmods.has('abstract') && has_body) {
|
|
probs.push(ParseProblem.Error(allmods.get('abstract'), 'Method declarations marked as abstract cannot have a method body'));
|
|
}
|
|
if (!is_interface_kind && !allmods.has('abstract') && !allmods.has('native') && !has_body) {
|
|
probs.push(ParseProblem.Error(method.nameToken, `Method '${method.name}' must have an implementation or be defined as abstract or native`));
|
|
}
|
|
if (!is_interface_kind && allmods.has('abstract') && !ownertypemods.has('abstract')) {
|
|
probs.push(ParseProblem.Error(allmods.get('abstract'), `Method '${method.name}' cannot be declared abstract inside a non-abstract type`));
|
|
}
|
|
if (is_interface_kind && has_body && !allmods.has('default')) {
|
|
probs.push(ParseProblem.Error(method.body[0], `Non-default interface methods cannot have a method body`));
|
|
}
|
|
if (allmods.has('native') && has_body) {
|
|
probs.push(ParseProblem.Error(allmods.get('native'), 'Method declarations marked as native cannot have a method body'));
|
|
}
|
|
// JLS8
|
|
if (type.typeKind !== 'interface' && allmods.has('default')) {
|
|
probs.push(ParseProblem.Error(allmods.get('default'), `Default method declarations are only allowed inside interfaces`));
|
|
}
|
|
if (allmods.has('default') && !has_body) {
|
|
probs.push(ParseProblem.Error(allmods.get('default'), `Default method declarations must have an implementation`));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {SourceConstructor} field
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkConstructorModifiers(field, probs) {
|
|
}
|
|
|
|
/**
|
|
* @param {SourceInitialiser} initialiser
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkInitialiserModifiers(initialiser, probs) {
|
|
}
|
|
|
|
/**
|
|
* @param {SourceType} type
|
|
* @param {ParseProblem[]} probs
|
|
*/
|
|
function checkTypeModifiers(type, probs) {
|
|
const typemods = new Map(type.modifierTokens.map(m => [m.source, m]));
|
|
checkDuplicate(type.modifierTokens, probs);
|
|
|
|
if (type.typeKind === 'interface' && typemods.has('final')) {
|
|
probs.push(ParseProblem.Error(typemods.get('final'), 'Interface declarations cannot be marked as final'));
|
|
}
|
|
if (type.typeKind === 'enum' && typemods.has('abstract')) {
|
|
probs.push(ParseProblem.Error(typemods.get('abstract'), 'Enum declarations cannot be marked as abstract'));
|
|
}
|
|
if (/[$]/.test(type._rawShortSignature)) {
|
|
checkConflictingAccess(type.modifierTokens, probs);
|
|
} else {
|
|
// top-level types cannot be private, protected or static
|
|
for (let mod of ['private','protected', 'static']) {
|
|
if (typemods.has(mod)) {
|
|
probs.push(ParseProblem.Error(typemods.get(mod), `Top-level declarations cannot be marked as ${mod}`));
|
|
}
|
|
}
|
|
}
|
|
|
|
type.fields.forEach(field => checkFieldModifiers(field, probs));
|
|
type.sourceMethods.forEach(method => checkMethodModifiers(type, typemods, method, probs));
|
|
type.constructors.forEach(ctr => checkConstructorModifiers(ctr, probs));
|
|
type.initers.forEach(initer => checkInitialiserModifiers(initer, probs));
|
|
}
|
|
|
|
/**
|
|
* @param {SourceType[]} types
|
|
*/
|
|
module.exports = function(types) {
|
|
const probs = [];
|
|
types.forEach(type => checkTypeModifiers(type, probs));
|
|
return probs;
|
|
}
|