version 1.2 (#93)

* initial working language server
* first hacky version of source parsing and type checking
* first iteration of method body parser
* add support for prefix/postfix inc expressions
* add basic support for parsing new expressions
* different attempt to parse using collapsable text ranges
* fix parsing of binary operstors following a bracket expression
* updated validation to use new JavaTypes module instead of MTIs
* add support for array-literal expressions
* fix || and && not being tokenized as operators allow float literals starting with dot
* add new method body parser to use direct linear parsing
* add super as an object literal
* fix interface constructors check constructor type modifiers
* fix assignment operator types
* Fix resolving of enclosed type identifiers
* add default constructor for class types with no explicit constructors
* add missing constructor validator
* add constructor parameters to list of resolvable types
* update SourceMethod to pass name in super constructor
* add Any* classes to reduce cascading errors
* update method call parameter checking use isTypeAssignable instead of getParameterCompatibleTypeSignatures
* tidy up isTypeAssignable allow class equivilents for primitives
* add more info when methods/ctrs cannot be matched
* allow interfaces to be cast to class instances
* use isTypeAssignable for checking branch test expressions
* allow AnyValue to be a constant value
* split shift operators from bitwise operators
* add support for literal numbers to be assignable to multiple primtive types
* clear diagnostics when document is closed
* update check for cast expression
* casting only applies to qualified term not a whole expression
* allow all primitive-number-type casts
* add support for synchronized statement
* update primitive type compatibility
* allow null to be cast to any non-primitve
* use better regex for string literals
* allow character literals to be assigned to number types
* add support for array qualifiers after a variable name
* make sure any long specifier is stripped from a bigint value
* improve invalid array expression message add AnyType array element to prevent cascading errors
* make default a modifer keyword for interface default method support
* initial support for wildcard type arguments
* fix parse issue with nested generic types
* allow generic types to be assigned to inherited types with compatible type arguments
* allow unicode characters, $ and _ in identifiers
* map primitive types to their boxed versions for class member
* support assert statement
* allow unicode char literals
* make type parser and body parser use same tokenizer
* reuse parsed tokens instead of tokenizing each method body
* re-add throws as a keyword
* treat default and synchronized as modifiers
* add SourceInitialiser support
* refactor to prepare for merging with type parsing
* add support for array qualifiers in type identifiers
* pass scoped type instead of method to typeIdent
* update ResolvableType to use same type resolving as method body parsing
* add support for post-name array qualifiers in fields and parameters
* post-name array qualifiers in method decls
* add type variables to SourceMethod
* initial attempt to support type variable arguments in methods
* specialise methods with type variables
* don't require default interface methods to be implemented
* make variable arity parameters an array type
* tidy array constructors and fix some warnings
* update isCallCompatible to handle variable arity calls
* improve assert statement support
* parse labels and break/continue targets
* refactor new term qualifiers
* add support for generic inferred-type arguments
* improve modifier checks for interface types
* improve reporting of unresolved type errors
* fix type checking of field and method declarations
* add missng strictfp modifier
* refactor in preparation for parsing local types
* replace Locals with scopeable MethodDeclarations to allow labels and types to be stored
* initial changes to support local type declarations
* update to use new set of SourceX classes
* refactor to allow expressions to have a type scope
* replace regex parsing with linear parsing
* generate source types before parsing
* fix support for resolving type variables in method declarations
* fix checking of array literal compatability
* report errors from unit parsing
* remove local modifier validation during parse add parameter modifier checking to validation
* allow trailing comma for array literals
* start separating validation from parsing
* add support for parsing enum values
* allow uppercase 0X in hex literals
* include enclosing types in identifier search
* add support for parsing parameterless lambdas
* ignore unresolved types in extends/implements
* implement specialisation of SourceType
* allow super as a member qualifier
* allow empty enums
* don't report missing constructors if superclass has none
* update typemap declarations to use CEIType instead of JavaType
* fix resolving of class type variables
* fix bad imports when resolving annotations
* allow null scope in findIdentifier
* add support for static member imports
* import types from same package
* remove this qualifier from isCastExpression
* add hex exponent support
* parse try-with-resources
* fix resolving imported enclosed types
* extract expression types into separate files
* extract statement types into separate files
* fix type warnings
* extract literals into separate files
* remove Value class, add NewExpression and separate out Any classes
* rename source types module
* remove some parse checks that should be in verify
* support token extraction in expressions
* implement resolveExpression
* add type cast checking
* check for valid type in class member expressions
* allow assigns for assignable type arguments
* improve reporting of unresolved identifiers
* add new array validation
* validate array literals
* validate array indexes
* improve validation of binary operators
* rename ResolvedType  to ResolvedValue
* improve checking of number literals
* support package name as a resolved value
* implement method body and ststement validation
* improve method call resolving
* add support for this() and super() constructor calls
* remove return type for source constructors
* add checks for unary operators
* ensure tokens are assigned for qualified expressions
* check castability using type assignments
* add implicit enum methods values() and valueOf()
* add basic type checking of lambda expressions
* fix return type check
* fix assert statement checks
* improve support for ternary operators in assignments and method invocations
* perform more detailed search of implemented methods
* initial test of context-dependant code completion
* support package, type and static field import completion
* support for member expressions
* use exact type signatures for locating types for completion items
* add support for field and method docs
* add support for docs in source types
* support member completion for array types improve comment formatting
* ensure Object is always last in the list of inherited types
* add owning method to statements create common keyword statement class
* improve code completion list add method parameters order list items by scope
* add source types to list hide this and super for non-methods
* fix bad member resolution at end of block fix missing method and type docs
* add support for editing multiple files
* allow multiple source files to be used in parsing
* load and parse files at startup
* add support for displaying method signatures
* add single trace function with timestamps
* implement shceduleReparse to reduce parsing load while typing
* remove parsed type list logging
* wait for reparsing before returning method signatures
* resolve new object contructors
* improve extraction of parameter docs
* update @types/vscode
* cache decoded android library in globalStoragePath
* load single android library cache from local folder
* android-29 library cache
* allow configurable app root setting
* set configurable trace logging and update section names
* description updates
* handle null token passed to ParseProblem
* refactoring
* Rename language client extension to Android
* ignore unnamed type declarations
* handle java file change notifications
* make sure we only try and parse java files
* add option to allow language server to be shutdown
* simplify handling of this and class member qualifiers
* relocate java-mti package into project
* get main node install to install langserver dependencies
* remove debugging pause
* rename body-parser3 to body-parser
* clean up import resolving code
* remove unused field from ResolvedImport
* remove validation modules that used old parser types
* remove old parser files
* remove redundant types and functions used by old parser
* move addproblem into TokenList
* remove unused ResolvedType class
* validate more statements
* add support for parsing and validating anonymous types
* hide some method modifiers which aren't useful to show
* code comments and minor improvements
* fix some type warnings
* improve support for completion of enum values
* add type name to parameter completion labels
* ignore synthetic members in completion list
* use a specialised map for handling case-insenstive file uris
* add basic build script
* reference java-mti package from GitHub
* revert @types/vscode
* update initial file loading to use URIs passed from the client changes to the appSourceRoot now require an extension restart
* add support for loading filtered androidx libraries for code completion
* update version of java-mti
* add mixpanel package
* add basic analytics
* fix dependency versions
* fix dependency versions
* set empty cache file markers
* add language server debug config
* add file to build script
* add unqualified type members when inside a method
* apply statics filter to enum values
* add basic debugger analytics
* include current time in startup event
* add terminate reason to debugger
* update changelog and readme
This commit is contained in:
Dave Holoway
2020-07-03 01:54:32 +01:00
committed by GitHub
parent d064b9a3f4
commit 83eda790be
89 changed files with 10265 additions and 47 deletions

View File

@@ -0,0 +1,55 @@
const { SourceType } = require('../source-types');
const ParseProblem = require('../parsetypes/parse-problem');
const { AnyType } = require('../anys');
/**
* @param {SourceType} source_type
* @param {*} probs
*/
function checkExtends(source_type, probs) {
const supertypes = source_type.extends_types
.map(st => st.resolved)
.filter(t => !(t instanceof AnyType));
if (supertypes.length === 0) {
return;
}
const supertype = supertypes[0];
if (source_type.typeKind === 'enum') {
probs.push(ParseProblem.Error(source_type.extends_types[0].tokens, `Enum types cannot declare a superclass`));
}
if (source_type.typeKind === 'class' && supertypes.length > 1) {
probs.push(ParseProblem.Error(source_type.extends_types[1].tokens, `Class types cannot inherit from more than one type`));
}
if (source_type.typeKind === 'class' && supertype.typeKind !== 'class') {
probs.push(ParseProblem.Error(source_type.extends_types[0].tokens, `Class '${source_type.fullyDottedRawName}' cannot inherit from ${supertype.typeKind} type: '${supertype.fullyDottedRawName}'`));
}
if (source_type.typeKind === 'class' && supertype.typeKind === 'class' && supertype.modifiers.includes('final')) {
probs.push(ParseProblem.Error(source_type.extends_types[0].tokens, `Class '${source_type.fullyDottedRawName}' cannot inherit from final class: '${supertype.fullyDottedRawName}'`));
}
if (source_type.typeKind === 'class' && supertype === source_type) {
probs.push(ParseProblem.Error(source_type.extends_types[0].tokens, `Class '${source_type.fullyDottedRawName}' cannot inherit from itself`));
}
if (source_type.typeKind === 'interface') {
supertypes.forEach((supertype, i) => {
if (supertype.typeKind !== 'interface') {
probs.push(ParseProblem.Error(source_type.extends_types[i].tokens, `Interface '${source_type.fullyDottedRawName}' cannot inherit from ${supertype.typeKind} type: '${supertype.fullyDottedRawName}'`));
}
if (supertype === source_type) {
probs.push(ParseProblem.Error(source_type.extends_types[i].tokens, `Interface '${source_type.fullyDottedRawName}' cannot inherit from itself`));
}
})
}
}
/**
* @param {SourceType[]} source_types
*/
module.exports = function(source_types) {
/** @type {ParseProblem[]} */
const probs = [];
source_types.forEach(type => checkExtends(type, probs));
return probs;
}

View File

@@ -0,0 +1,43 @@
const ParseProblem = require('../parsetypes/parse-problem');
const {SourceType} = require('../source-types');
const { AnyType } = require('../anys');
const { UnresolvedType } = require('java-mti');
/**
* @param {SourceType} source_type
* @param {*} probs
*/
function checkImplements(source_type, probs) {
const superinterfaces = source_type.implements_types
.map(st => st.resolved)
.filter(t => !(t instanceof AnyType));
if (superinterfaces.length === 0) {
return;
}
if (source_type.typeKind === 'interface') {
probs.push(ParseProblem.Error(source_type.implements_types[0].tokens, `Interface types cannot declare an implements section`));
}
if (source_type.typeKind === 'class') {
superinterfaces.forEach((intf, i) => {
if (intf instanceof UnresolvedType) {
return;
}
if (intf.typeKind !== 'interface') {
probs.push(ParseProblem.Error(source_type.implements_types[i].tokens, `Class '${source_type.fullyDottedRawName}' cannot implement ${intf.typeKind} type: '${intf.fullyDottedRawName}'`));
}
})
}
}
/**
* @param {SourceType[]} source_types
*/
module.exports = function(source_types) {
/** @type {ParseProblem[]} */
const probs = [];
source_types.forEach(type => checkImplements(type, probs));
return probs;
}

View File

@@ -0,0 +1,65 @@
const ParseProblem = require('../parsetypes/parse-problem');
const {SourceType, SourceAnnotation} = require('../source-types');
const {CEIType, Method} = require('java-mti');
/**
* @param {SourceType} source_type
* @param {*} probs
*/
function checkOverrides(source_type, probs) {
if (source_type.extends_types.length === 0) {
return;
}
if (source_type.typeKind !== 'class') {
return;
}
/** @type {{ann:SourceAnnotation, method:Method, method_id:string}[]} */
const overriden_methods = [];
source_type.sourceMethods.reduce((arr, method) => {
const ann = method.annotations.find(a => a.type.simpleTypeName === 'Override');
if (ann) {
arr.push({
ann,
method,
method_id: `${method.name}${method.methodSignature}`,
})
}
return arr;
}, overriden_methods);
if (!overriden_methods.length) {
return;
}
const methods = new Set(), supers_done = new Set();
const supers = source_type.supers.slice();
while (supers.length) {
const s = supers.shift();
supers_done.add(s);
s.methods.forEach(m => {
methods.add(`${m.name}${m.methodSignature}`);
});
if (s instanceof CEIType) {
s.supers.filter(s => !supers_done.has(s)).forEach(s => supers.push(s));
}
}
overriden_methods.forEach(x => {
if (!methods.has(x.method_id)) {
probs.push(ParseProblem.Error(x.ann.annotationTypeIdent.tokens, `${x.method.label} does not override a matching method in any inherited type or interface`));
}
})
}
/**
* @param {SourceType[]} source_types
*/
module.exports = function(source_types) {
/** @type {ParseProblem[]} */
const probs = [];
source_types.forEach(type => checkOverrides(type, probs));
return probs;
}

View File

@@ -0,0 +1,53 @@
const { SourceType, SourceTypeIdent } = require('../source-types');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {SourceTypeIdent} type
* @param {boolean} is_return_type
* @param {ParseProblem[]} probs
*/
function checkType(type, is_return_type, probs) {
const typesig = type.resolved.typeSignature;
if (/^\[*U/.test(typesig)) {
probs.push(ParseProblem.Error(type.tokens, `Unresolved type '${type.resolved.label}'`))
return;
}
if (typesig === 'V' && !is_return_type) {
probs.push(ParseProblem.Error(type.tokens, `'void' is not a valid type for variables`))
}
if (/^\[+V/.test(typesig)) {
probs.push(ParseProblem.Error(type.tokens, `Illegal type: '${type.resolved.label}'`))
}
}
/**
* @param {SourceType} type
* @param {*} probs
*/
function checkInvalidTypes(type, probs) {
type.fields.forEach(f => checkType(f.fieldTypeIdent, false, probs));
type.sourceMethods.forEach(m => {
checkType(m.returnTypeIdent, true, probs);
m.parameters.forEach(p => {
checkType(p.paramTypeIdent, false, probs);
})
})
type.constructors.forEach(c => {
c.parameters.forEach(p => {
checkType(p.paramTypeIdent, false, probs);
})
})
}
/**
* @param {SourceType[]} source_types
*/
module.exports = function(source_types) {
/** @type {ParseProblem[]} */
const probs = [];
source_types.forEach(type => checkInvalidTypes(type, probs));
return probs;
}

View File

@@ -0,0 +1,40 @@
const {SourceType, SourceConstructor} = require('../source-types');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {SourceType} source_type
* @param {ParseProblem[]} probs
*/
function checkConstructor(source_type, probs) {
if (source_type.typeKind !== 'class') {
return;
}
if (source_type.constructors[0] instanceof SourceConstructor) {
return;
}
const superclass = source_type.supers.find(s => s.typeKind === 'class');
if (!superclass) {
// if there's no superclass, the class must inherit from an interface
// - which means the inherited class is Object (and a default constructor exists)
return;
}
if (superclass.constructors.length) {
if (!superclass.constructors.find(c => c.parameterCount === 0)) {
// the source type has no declared constructors, but the superclass
// does not include a default (parameterless) constructor
probs.push(ParseProblem.Error(source_type.nameToken, `Class '${source_type.fullyDottedRawName}' requires a constructor to be declared because the inherited class '${superclass.fullyDottedRawName}' does not define a default constructor.`));
}
}
}
/**
* @param {SourceType[]} source_types
*/
module.exports = function(source_types) {
/** @type {ParseProblem[]} */
const probs = [];
source_types.forEach(type => checkConstructor(type, probs));
return probs;
}

View File

@@ -0,0 +1,188 @@
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;
}

View File

@@ -0,0 +1,85 @@
const ParseProblem = require('../parsetypes/parse-problem');
const { SourceType } = require('../source-types');
const { CEIType, Method} = require('java-mti');
const {isTypeAssignable} = require('../expression-resolver');
function nonAbstractLabel(label) {
return label.replace(/\babstract /g, '');
}
/**
*
* @param {Method} impl method implementation
* @param {Method} method interface method
*/
function isMethodCompatible(impl, method) {
const impl_params = impl.parameters;
const method_params = method.parameters;
if (impl_params.length !== method_params.length) {
return false;
}
return impl_params.every((p,idx) => isTypeAssignable(method_params[idx].type, p.type))
&& isTypeAssignable(method.returnType, impl.returnType);
}
/**
* @param {SourceType} source_type
* @param {*} probs
*/
function checkImplementedInterfaces(source_type, probs) {
if (source_type.implements_types.length === 0) {
return;
}
if (source_type.typeKind === 'interface') {
return;
}
if (source_type.modifiers.includes('abstract')) {
return;
}
/** @type {Set<CEIType>} */
const interfaces = new Set(), supers_done = new Set();
const supers = source_type.supers.slice();
while (supers.length) {
const s = supers.shift();
supers_done.add(s);
if (s instanceof CEIType) {
if (s.typeKind === 'interface') {
interfaces.add(s);
}
s.supers.filter(s => !supers_done.has(s)).forEach(s => supers.push(s));
}
}
const implemented = source_type.methods.map(m => `${m.name}${m.methodSignature}`);
interfaces.forEach((intf, i) => {
const missing_methods = [];
intf.methods.forEach(m => {
// default methods don't require implementing
if (m.hasImplementation) {
return;
}
const namedsig = `${m.name}${m.methodSignature}`
if (implemented.indexOf(namedsig) < 0) {
// perform a more detailed search for a compatible match
if (!source_type.methods.find(source_method => source_method.name === m.name && isMethodCompatible(source_method, m))) {
missing_methods.push(nonAbstractLabel(m.label));
}
}
})
if (missing_methods.length) {
probs.push(ParseProblem.Error(source_type.kind_token, `Non-abstract ${source_type.typeKind} '${source_type.fullyDottedRawName}' does not implement the following methods from interface '${intf.fullyDottedTypeName}':\n${missing_methods.join('\n')}`));
}
});
}
/**
* @param {SourceType[]} source_types
*/
module.exports = function(source_types) {
/** @type {ParseProblem[]} */
const probs = [];
source_types.forEach(type => checkImplementedInterfaces(type, probs));
return probs;
}

View File

@@ -0,0 +1,17 @@
const { SourceUnit } = require('../source-types');
const ParseProblem = require('../parsetypes/parse-problem');
/**
* @param {SourceUnit} unit
*/
module.exports = function(mod, unit) {
/** @type {ParseProblem[]} */
const probs = [];
unit.imports.forEach(i => {
if (!i.resolved)
probs.push(ParseProblem.Warning(i.nameTokens, `Unresolved import: ${i.package_name}`));
})
return probs;
}