use of org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot in project n4js by eclipse.
the class CanLoadFromDescriptionHelper method dependsOnAny.
/**
* Checks if the resource denoted by {@code thisURI} has a transitive dependency to any of the resources in others.
* If others is empty, returns false.
*
* Implements a BFS algorithm.
*
* The direct dependencies are taken from the index. If a resource is missing form the index, we consider a
* dependency to exist so in that sense we are pessimistic. If a resource description is available from the index
* but does not have any dependency information, we consider a dependency to exist, too.
*
* @param thisURI
* the URI under consideration.
* @param candidates
* the URIs to be checked against
* @param context
* the context resource set used for obtaining the workspace configuration.
* @param index
* the index to be used.
* @param considerOnlySameProject
* flag to consider / ignore project boundaries.
* @return true, if this resource has a transitive dependency to any of the others.
*/
protected boolean dependsOnAny(URI thisURI, Set<URI> candidates, ResourceSet context, IResourceDescriptions index, boolean considerOnlySameProject) {
N4JSProjectConfigSnapshot thisProject = null;
if (considerOnlySameProject && !candidates.isEmpty()) {
// early check whether the candidates stem from the same project as the requested thisURI
// (note: this is based on the assumption that there cannot be a cyclic dependency between modules of
// different projects, because cyclic dependencies between projects are disallowed)
thisProject = workspaceAccess.findProjectByNestedLocation(context, thisURI);
candidates = filterCandidatesByProject(candidates, thisProject);
}
// are there any relevant candidates at all?
if (candidates.isEmpty()) {
return false;
}
// Keep track of all visited URIs
final Set<URI> visited = Sets.newHashSet();
// breadth first search since it is more likely to find resources from the same project
// in our own dependencies rather than in the transitive dependencies
final Queue<URI> queue = new ArrayDeque<>();
// the starting point. It is deliberately not added to the visited resources
// to allow to detect cycles.
queue.add(thisURI);
while (!queue.isEmpty()) {
// try to find the direct dependencies for the next URI in the queue
Optional<List<String>> dependencies = readDirectDependencies(index, queue.poll());
if (!dependencies.isPresent()) {
// none found - be pessimistic and announce a dependency
return true;
}
// traverse the direct dependencies
for (String dependency : dependencies.get()) {
// and convert each string based dependency to a URI
URI dependencyURI = URI.createURI(dependency);
// mark the dependency as visited and if its the first occurrence
if (visited.add(dependencyURI)) {
// or does the initial URI and the current candidate stem from the same project?
if (!considerOnlySameProject || projectContainsURI(thisProject, dependencyURI)) {
// it is part of the interesting resources, return true
if (candidates.contains(dependencyURI)) {
return true;
}
// enque the dependency
queue.add(dependencyURI);
}
}
}
}
// to one of the candidates
return false;
}
use of org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot in project n4js by eclipse.
the class ProjectImportEnablingScope method findModulesInProject.
/**
* Finds all modules with the given module FQN (without project name) in the given project. Supports two special
* cases:
* <ol>
* <li>if the module name is absent, the given project's main module will be returned.
* <li>if a {@link ProjectDescription#getDefinesPackage() definition project} exists for the given project, modules
* of the definition project will be returned and only if that fails, modules of the given project will be returned.
* </ol>
*/
private Collection<IEObjectDescription> findModulesInProject(Optional<QualifiedName> moduleName, N4JSPackageName projectName) {
boolean useMainModule = !moduleName.isPresent();
N4JSProjectConfigSnapshot targetProject = findProject(projectName, contextProject, true);
QualifiedName moduleNameToSearch = useMainModule ? ImportSpecifierUtil.getMainModuleOfProject(targetProject) : moduleName.get();
Collection<IEObjectDescription> result;
result = moduleNameToSearch != null ? getElementsWithDesiredProjectName(moduleNameToSearch, targetProject.getN4JSPackageName()) : Collections.emptyList();
if (result.isEmpty() && targetProject != null && !Objects.equals(targetProject.getN4JSPackageName(), projectName)) {
// -> as a fall back, try again in project we asked for (i.e. the defined project)
if (useMainModule) {
targetProject = findProject(projectName, contextProject, false);
moduleNameToSearch = ImportSpecifierUtil.getMainModuleOfProject(targetProject);
} else {
// leave 'moduleNameToSearch' unchanged
}
result = moduleNameToSearch != null ? getElementsWithDesiredProjectName(moduleNameToSearch, projectName) : Collections.emptyList();
}
return result;
}
use of org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot in project n4js by eclipse.
the class ProjectImportEnablingScope method computeImportType.
/**
* Convenience method over
* {@link ImportSpecifierUtil#computeImportType(QualifiedName, boolean, N4JSProjectConfigSnapshot)}
*/
private ModuleSpecifierForm computeImportType(QualifiedName name, N4JSProjectConfigSnapshot project) {
final String firstSegment = name.getFirstSegment();
final N4JSProjectConfigSnapshot targetProject = findProject(new N4JSPackageName(firstSegment), project, true);
final boolean firstSegmentIsProjectName = targetProject != null;
return ImportSpecifierUtil.computeImportType(name, firstSegmentIsProjectName, targetProject);
}
use of org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot in project n4js by eclipse.
the class ProjectImportEnablingScope method getSingleElement.
@Override
public IEObjectDescription getSingleElement(QualifiedName name) {
final List<IEObjectDescription> result = Lists.newArrayList(getElements(name));
int size = result.size();
// handle combination of .js / .cjs / .mjs files with same base name
if (size > 1) {
removeSuperfluousPlainJsFiles(result);
size = result.size();
}
if (size == 1) {
// main case
return result.get(0);
}
// use sorted entries and linked map for determinism in error message
Collections.sort(result, Comparator.comparing(IEObjectDescription::getEObjectURI, Comparator.comparing(URI::toString)));
final Map<IEObjectDescription, N4JSProjectConfigSnapshot> descriptionsToProject = new LinkedHashMap<>();
for (IEObjectDescription objDescr : result) {
URI uri = objDescr.getEObjectURI();
N4JSProjectConfigSnapshot n4jsdProject = workspaceConfigSnapshot.findProjectContaining(uri);
descriptionsToProject.put(objDescr, n4jsdProject);
}
IEObjectDescription overwritingModule = handleCollisions(result, descriptionsToProject);
if (overwritingModule != null) {
return overwritingModule;
}
// if no import declaration was given, we skip the advanced error reporting
if (!importDeclaration.isPresent()) {
return null;
}
// handle special defaults
IEObjectDescription defaultModule = handleDefaults(descriptionsToProject);
if (defaultModule != null) {
return defaultModule;
}
// handle error cases to help user fix the issue
StringBuilder sbErrrorMessage = new StringBuilder("Cannot resolve ");
ModuleSpecifierForm importType = computeImportType(name, this.contextProject);
switch(importType) {
case PROJECT:
sbErrrorMessage.append("project import");
break;
case COMPLETE:
sbErrrorMessage.append("complete module specifier (with project name as first segment)");
break;
case PLAIN:
sbErrrorMessage.append("plain module specifier (without project name as first segment)");
break;
case PROJECT_NO_MAIN:
sbErrrorMessage.append("project import: target project does not define a main module");
break;
default:
sbErrrorMessage.append("module specifier");
break;
}
if (!importType.equals(ModuleSpecifierForm.PROJECT_NO_MAIN)) {
sbErrrorMessage.append(": ");
if (size == 0) {
sbErrrorMessage.append("no matching module found");
} else {
sbErrrorMessage.append("multiple matching modules found: ");
String matchingModules = "";
for (IEObjectDescription descr : descriptionsToProject.keySet()) {
if (!matchingModules.isEmpty()) {
matchingModules += ", ";
}
if (descr.getEObjectURI() != null) {
URI uri = descr.getEObjectURI().trimFragment();
URI relUri = uri.deresolve(workspaceConfigSnapshot.getPath());
Path relPath = Path.of(relUri.toFileString());
relPath = relPath.subpath(1, relPath.getNameCount());
matchingModules += relPath.toString();
} else {
matchingModules += descriptionsToProject.get(descr).getPackageName() + "/" + descr.getQualifiedName().toString();
}
}
sbErrrorMessage.append(matchingModules);
}
}
sbErrrorMessage.append('.');
final EObject originalProxy = (EObject) this.importDeclaration.get().eGet(N4JSPackage.eINSTANCE.getImportDeclaration_Module(), false);
return new IssueCodeBasedEObjectDescription(EObjectDescription.create("impDecl", originalProxy), sbErrrorMessage.toString(), IssueCodes.IMP_UNRESOLVED);
}
use of org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot in project n4js by eclipse.
the class ProjectImportEnablingScope method handleCollisions.
/**
* Special case handling when we have a definition and a pure JS file in the scope. In such cases we return with the
* description that corresponds to the definition file. Given several modules with the same name, there exists a
* resolution only iff:
* <ul>
* <li/>There is a plain js module, and
* <li/>An N4JSD module must be the corresponding definition module inside the corresponding definition project, and
* <li/>An d.ts module must be the corresponding definition module inside the corresponding definition project
* </ul>
* In case both an N4JSD and a d.ts module exist, the n4jsd module is returned. Otherwise either the N4JSD or the
* d.ts module is returned.
*/
private IEObjectDescription handleCollisions(List<IEObjectDescription> result, Map<IEObjectDescription, N4JSProjectConfigSnapshot> descriptionsToProject) {
Set<String> considerExtensions = ImmutableSet.<String>builder().addAll(ALL_JS_FILE_EXTENSIONS).add(N4JSD_FILE_EXTENSION).add(DTS_FILE_EXTENSION).build();
Map<String, IEObjectDescription> descr4Ext = new HashMap<>();
Map<String, N4JSProjectConfigSnapshot> prj4Ext = new HashMap<>();
for (IEObjectDescription res : result) {
String ext = URIUtils.fileExtension(res.getEObjectURI());
if (!considerExtensions.contains(ext)) {
continue;
}
N4JSProjectConfigSnapshot prj = descriptionsToProject.get(res);
if (descr4Ext.containsKey(ext)) {
// return null due to conflict
return null;
}
if (ext != null && prj != null) {
descr4Ext.put(ext, res);
prj4Ext.put(ext, prj);
}
}
if (descr4Ext.size() < 2) {
// return null due to missing project information
return null;
}
// NOTE: the priority between .js/.cjs/.mjs we implement in the following loop based on the order in constant
// ALL_JS_FILE_EXTENSIONS does not have an effect in practice, because conflicts between .js/.cjs/.mjs files are
// resolved up front (see #removeSuperfluousPlainJsFiles() and its invocation in #getSingleElement()), meaning
// we will always have only one of .js/.cjs/.mjs when reaching this point in the code:
N4JSProjectConfigSnapshot jsProject = null;
for (String jsFileExt : ALL_JS_FILE_EXTENSIONS) {
N4JSProjectConfigSnapshot removed = prj4Ext.remove(jsFileExt);
if (removed != null) {
jsProject = removed;
}
}
if (jsProject == null) {
// return null due to missing implementation module
return null;
}
Iterator<Entry<String, N4JSProjectConfigSnapshot>> iter = prj4Ext.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, N4JSProjectConfigSnapshot> entry = iter.next();
String ext = entry.getKey();
N4JSProjectConfigSnapshot prj = entry.getValue();
if (!Objects.equals(jsProject.getPathAsFileURI(), prj.getPathAsFileURI())) {
// case both modules are in different projects: check here iff related
switch(ext) {
case N4JSD_FILE_EXTENSION:
if (prj.getType() != ProjectType.DEFINITION || !Objects.equals(prj.getDefinesPackage(), new N4JSPackageName(jsProject.getName()))) {
// return null due to conflict
return null;
}
break;
case DTS_FILE_EXTENSION:
if (prj.getType() != ProjectType.PLAINJS || !prj.getName().endsWith("/" + jsProject.getPackageName())) {
// return null due to conflict
return null;
}
break;
default:
}
} else {
// case both modules are in same project: assume one being the definition of the other
}
}
if (prj4Ext.size() == 1) {
String dExt = prj4Ext.keySet().iterator().next();
return descr4Ext.get(dExt);
} else if (prj4Ext.size() == 2) {
if (descr4Ext.containsKey(N4JSD_FILE_EXTENSION)) {
// paranoia check - should always be true
return descr4Ext.get(N4JSD_FILE_EXTENSION);
}
if (descr4Ext.containsKey(DTS_FILE_EXTENSION)) {
// paranoia check - should always be true
return descr4Ext.get(DTS_FILE_EXTENSION);
}
}
return null;
}
Aggregations