I'm trying to learn Xtext by defining a grammar with java-like packages, classes and imports. A fragment of my grammar looks like this with CompilationUnit being the root object.
CompilationUnit:
packageDeclaration=PackageDeclaration?
imports+=ImportDeclaration*
topClass=Class
;
packageDeclaration:
'package' path=QualifiedName ';'
;
ImportDeclaration:
'import' importedNamespace=QualifiedNameWithWildCard ';'
;
Class:
{Class} visibility=Visibility isStatic?='static' 'class' name=ID ('extends' superClass=[Class|QualifiedName])? body=ClassBody
;
For importing cross-references I use the DefaultGlobalScopeProvider and I have overriden the QualifiedNameProvider with my own version that appends the package name as a prefix for the QualifiedName of topClass. For automating the own package import I edited the project specific ScopeProvider. All this seems to be working beatifully and using the generated Eclipse IDE I am able to import classes from other files using "import [packageName].[*|ClassName]". (Still work in progress on automatic imports for Classes in the same package with the referrer, but for the moment I can manage with explicit import)
The next step I'm trying is the validation of the import and package declarations. I want to implement the same limitation as Java that the package declaration of a file should equal to the file's relative path and on the other hand I want to validate the existance of an imported Class or package. The problem is that through the eResource of an EObject I am only able to access the resources full URI (e.g. platform:/resource/Sample/src/mypack/Sample.myjava) while the path name relative to the source folder would be shorter (mypack/Sample.myjava). I haven't yet figured out if I should clip the URI with some logic or have some completely different approach.
One possible idea would perhaps be to somehow gain the URIs of each classpath directory of the project and work from there, but I haven't yet figured out how to do that.
Any idea how I should validate my package and import declarations? I keep having the feeling that I'm very close, but yet so far.
Edit: Deleted a missthought on DefaultGlobalScopeProvider behaviour. That is not related to file hierarchy, but only qualified names. Also I got the automatic imports of own package to work.
Update: Thinking of it, I should propably validate imports just by enumerating available resources and checking their qualified names. Then only the package declaration validation would require file hierarchy checks.
Update2: It seems that in eclipse the resource URI is always in the format "platform:/resource/[Project]/[src folder]/...". Assuming this would mean that I could just hard check against that, but doing this in the grammar project validator would create a grammar-level dependency on Eclipse, which would propably not be a good idea for any serious DSL project. However a comment in this post led me to think that maybe I should consider not doing the package location validation at all in the grammar level, but only in the UI project (which I haven't altered yet). The idea is that the location of a resource should perhaps be left in more abstracted form than expecting a traditional file system hierarchy.
I was able to create a solution. The following code is a crude implementation of validating import declarations that may need some cleanup, but basically does the thing.
@Check
def checkImportSanity(ImportDeclaration imp) {
val importQN = qualifiedNameConverter.toQualifiedName(imp.importedNamespace)
val hasWildcard = isImportWildcard(importQN.getLastSegment)
for (r:imp.eContainer.eResource.resourceSet.resources) {
val classQN = qualifiedNameProvider.getFullyQualifiedName((r.getContents().get(0) as CompilationUnit).topClass)
if (hasWildcard && classQN.skipLast(1).equals(importQN.skipLast(1))) {
return
}
else if (classQN.equals(importQN)) {
return
}
}
error("This import doesn't match any class!",
imp, MyJavaPackage.Literals.IMPORT_DECLARATION__IMPORTED_NAMESPACE
);
}
In short it just loops through all the resources and compares the qualified names of the import and the resource's top class ignoring any file names. The qualifiedNameProvider is my own that appends the package name to the class name. Like this it can only validate top class level imports, but I can go further later. No idea if this would be enough for importing external libraries, but that is another topic I am not interested at the moment.
Furthermore I decided to not validate package names against file names. While Java does this, I guess in today's world it might be better to leave this as an editor level constraint and ignore it on grammar level.