Search in sources :

Example 31 with N4JSProjectConfigSnapshot

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;
}
Also used : N4JSProjectConfigSnapshot(org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot) List(java.util.List) URI(org.eclipse.emf.common.util.URI) ArrayDeque(java.util.ArrayDeque)

Example 32 with N4JSProjectConfigSnapshot

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;
}
Also used : N4JSProjectConfigSnapshot(org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot) QualifiedName(org.eclipse.xtext.naming.QualifiedName) IEObjectDescription(org.eclipse.xtext.resource.IEObjectDescription)

Example 33 with N4JSProjectConfigSnapshot

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);
}
Also used : N4JSPackageName(org.eclipse.n4js.workspace.utils.N4JSPackageName) N4JSProjectConfigSnapshot(org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot)

Example 34 with N4JSProjectConfigSnapshot

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);
}
Also used : Path(java.nio.file.Path) URI(org.eclipse.emf.common.util.URI) IEObjectDescription(org.eclipse.xtext.resource.IEObjectDescription) LinkedHashMap(java.util.LinkedHashMap) ModuleSpecifierForm(org.eclipse.n4js.n4JS.ModuleSpecifierForm) N4JSProjectConfigSnapshot(org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot) EObject(org.eclipse.emf.ecore.EObject)

Example 35 with N4JSProjectConfigSnapshot

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;
}
Also used : Entry(java.util.Map.Entry) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) N4JSPackageName(org.eclipse.n4js.workspace.utils.N4JSPackageName) N4JSProjectConfigSnapshot(org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot) IEObjectDescription(org.eclipse.xtext.resource.IEObjectDescription)

Aggregations

N4JSProjectConfigSnapshot (org.eclipse.n4js.workspace.N4JSProjectConfigSnapshot)50 URI (org.eclipse.emf.common.util.URI)15 N4JSPackageName (org.eclipse.n4js.workspace.utils.N4JSPackageName)9 N4JSSourceFolderSnapshot (org.eclipse.n4js.workspace.N4JSSourceFolderSnapshot)8 Path (java.nio.file.Path)7 Resource (org.eclipse.emf.ecore.resource.Resource)7 N4JSWorkspaceConfigSnapshot (org.eclipse.n4js.workspace.N4JSWorkspaceConfigSnapshot)7 FileURI (org.eclipse.n4js.workspace.locations.FileURI)7 ArrayList (java.util.ArrayList)6 IEObjectDescription (org.eclipse.xtext.resource.IEObjectDescription)6 File (java.io.File)5 HashMap (java.util.HashMap)5 N4JSResource (org.eclipse.n4js.resource.N4JSResource)5 LinkedHashMap (java.util.LinkedHashMap)4 ResourceSet (org.eclipse.emf.ecore.resource.ResourceSet)4 ProjectDescription (org.eclipse.n4js.packagejson.projectDescription.ProjectDescription)4 TModule (org.eclipse.n4js.ts.types.TModule)4 QualifiedName (org.eclipse.xtext.naming.QualifiedName)4 IResourceDescription (org.eclipse.xtext.resource.IResourceDescription)4 IResourceDescriptions (org.eclipse.xtext.resource.IResourceDescriptions)4