From bac0c5f48d7c385d7c44c8b02571823937b10d06 Mon Sep 17 00:00:00 2001 From: mmews Date: Thu, 29 Feb 2024 15:48:02 +0100 Subject: [PATCH] migrate migrate --- .../tooling/organizeImports/DIUtility.java | 190 +++++++++++ .../tooling/organizeImports/DIUtility.xtend | 151 --------- .../organizeImports/ImportSpecifiersUtil.java | 209 ++++++++++++ .../ImportSpecifiersUtil.xtend | 191 ----------- .../ImportStateCalculator.java | 318 ++++++++++++++++++ .../ImportStateCalculator.xtend | 243 ------------- .../validators/N4JSImportValidator.xtend | 2 +- 7 files changed, 718 insertions(+), 586 deletions(-) create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend create mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java delete mode 100644 plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java new file mode 100644 index 0000000000..cb0185aa31 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.java @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tooling.organizeImports; + +import static org.eclipse.n4js.AnnotationDefinition.BINDER; +import static org.eclipse.n4js.AnnotationDefinition.GENERATE_INJECTOR; +import static org.eclipse.n4js.AnnotationDefinition.INJECT; +import static org.eclipse.n4js.AnnotationDefinition.USE_BINDER; +import static org.eclipse.n4js.AnnotationDefinition.WITH_PARENT_INJECTOR; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.n4ProviderType; +import static org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.newRuleEnvironment; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.n4js.AnnotationDefinition; +import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef; +import org.eclipse.n4js.ts.typeRefs.TypeRef; +import org.eclipse.n4js.ts.types.TAnnotation; +import org.eclipse.n4js.ts.types.TAnnotationArgument; +import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument; +import org.eclipse.n4js.ts.types.TClass; +import org.eclipse.n4js.ts.types.Type; +import org.eclipse.n4js.typesystem.utils.AllSuperTypesCollector; +import org.eclipse.n4js.utils.DeclMergingHelper; + +/** + * Collection of utility methods for working with N4JS DI. + */ +public class DIUtility { + + /***/ + public static boolean isSingleton(Type type) { + return exists(type.getAnnotations(), a -> AnnotationDefinition.SINGLETON.name.equals(a.getName())); + } + + /***/ + public static boolean hasSuperType(Type type) { + if (type instanceof TClass) { + TClass tc = (TClass) type; + if (tc.getSuperClassRef() != null) { + return tc.getSuperClassRef().getDeclaredType() instanceof TClass; + } + } + return false; + } + + /** + * Returns true if provided type is instanceof {@link TClass} and at least owned member is annotated with + * {@link AnnotationDefinition#INJECT}. + */ + public static boolean hasInjectedMembers(Type type, DeclMergingHelper declMergingHelper) { + if (type instanceof TClass) + return exists(AllSuperTypesCollector.collect((TClass) type, declMergingHelper), + t -> exists(t.getOwnedMembers(), m -> INJECT + .hasAnnotation(type))); + else + return false; + } + + /** + * Generate DI meta info for classes that have injected members, or are can be injected and have DI relevant + * information, e.g. scope annotation. Also if type has a super type (injection of inherited members) + */ + public static boolean isInjectedClass(Type type, DeclMergingHelper declMergingHelper) { + return isSingleton(type) || hasInjectedMembers(type, declMergingHelper) || hasSuperType(type); + } + + /***/ + public static boolean isBinder(Type type) { + return BINDER.hasAnnotation(type); + } + + /***/ + public static boolean isDIComponent(Type type) { + return GENERATE_INJECTOR.hasAnnotation(type); + } + + /** + * Checks if diComponent has parent component, that is one specified by the superclass or by the inheritance. + */ + public static boolean hasParentInjector(Type type) { + if (WITH_PARENT_INJECTOR.hasAnnotation(type)) { + return true; + } + + if (type instanceof TClass) { + return ((TClass) type).getSuperClassRef() != null; + } else + return false; + } + + /** + * @returns {@link Type} of the parent DIComponent. Throws {@link RuntimeException} if no parent on provided type. + */ + public static Type findParentDIC(Type type) { + TypeRef parent = null; + if (WITH_PARENT_INJECTOR.hasAnnotation(type)) { + TAnnotation ann = WITH_PARENT_INJECTOR.getOwnedAnnotation(type); + if (ann != null) { + parent = ((TAnnotationTypeRefArgument) ann.getArgs().get(0)).getTypeRef(); + } + + } else if (type instanceof TClass) { + parent = ((TClass) type).getSuperClassRef(); + } + + if (parent != null) { + return parent.getDeclaredType(); + } + + throw new RuntimeException("no parent on " + type.getName()); + } + + /** + * returns list of types that are parameters of {@link AnnotationDefinition#USE_BINDER} annotations attached to a + * given type or empty list + */ + public static List resolveBinders(Type type) { + List argTypes = new ArrayList<>(); + for (TAnnotation ann : USE_BINDER.getAllOwnedAnnotations(type)) { + for (TAnnotationArgument annArg : ann.getArgs()) { + Type argType = ((TAnnotationTypeRefArgument) annArg).getTypeRef().getDeclaredType(); + if (argType != null) { + argTypes.add(argType); + } + } + } + + return argTypes; + } + + /** + * Returns with {@code true} if one or more members of the given type are annotated with {@code @Inject} annotation. + */ + public static boolean requiresInjection(Type type, DeclMergingHelper declMergingHelper) { + if (type instanceof TClass) { + return exists(AllSuperTypesCollector.collect((TClass) type, declMergingHelper), + tc -> exists(tc.getOwnedMembers(), m -> INJECT.hasAnnotation(m))); + } + return false; + } + + /** + * Returns with {@code true} if the type reference argument requires injection. Either the declared type requires + * injection, or the type reference represents an N4 provider, and the dependency of the provider requires + * injection. Otherwise returns with {@code false}. + */ + public static boolean requiresInjection(TypeRef tr, DeclMergingHelper declMergingHelper) { + return requiresInjection(tr.getDeclaredType(), declMergingHelper) + || isProviderType(tr) && requiresInjection(getProvidedType(tr), declMergingHelper); + } + + /** + * Returns with {@code true} if the type reference argument is an N4 provider. Otherwise returns with {@code false}. + * Also returns with {@code false} if the type reference is sub interface of N4 provider or a class which implements + * the N4 provider interface. + */ + public static boolean isProviderType(TypeRef tr) { + return null != tr && tr.getDeclaredType() == n4ProviderType(newRuleEnvironment(tr)); + } + + /** + * Returns with the type most nested dependency if the type reference argument represents and N4 provider. Otherwise + * returns with {@code null}. + */ + public static Type getProvidedType(TypeRef tr) { + if (!isProviderType(tr)) { + return null; + } + TypeRef nestedTypeRef = tr; + while (isProviderType(nestedTypeRef) && nestedTypeRef instanceof ParameterizedTypeRef) { + List typeArgs = toList( + filter(((ParameterizedTypeRef) nestedTypeRef).getDeclaredTypeArgs(), TypeRef.class)); + nestedTypeRef = (typeArgs.isEmpty()) ? null : typeArgs.get(0); + } + return nestedTypeRef == null ? null : nestedTypeRef.getDeclaredType(); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend deleted file mode 100644 index 72376157a3..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/DIUtility.xtend +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tooling.organizeImports - -import java.util.List -import org.eclipse.n4js.AnnotationDefinition -import org.eclipse.n4js.ts.typeRefs.ParameterizedTypeRef -import org.eclipse.n4js.ts.typeRefs.TypeRef -import org.eclipse.n4js.ts.types.TAnnotationTypeRefArgument -import org.eclipse.n4js.ts.types.TClass -import org.eclipse.n4js.ts.types.Type -import org.eclipse.n4js.typesystem.utils.AllSuperTypesCollector -import org.eclipse.n4js.utils.DeclMergingHelper - -import static org.eclipse.n4js.AnnotationDefinition.* - -import static extension org.eclipse.n4js.typesystem.utils.RuleEnvironmentExtensions.* - -/** - * Collection of utility methods for working with N4JS DI. - */ -class DIUtility { - - def public static boolean isSingleton(Type type) { - type.annotations.exists[name == AnnotationDefinition.SINGLETON.name] - } - - def public static boolean hasSuperType(Type type) { - if(type instanceof TClass){ - type.superClassRef?.declaredType instanceof TClass - }else{ - false - } - } - - /** - * Returns true if provided type is instanceof {@link TClass} - * and at least owned member is annotated with {@link AnnotationDefinition#INJECT}. - */ - def public static boolean hasInjectedMembers(Type type, DeclMergingHelper declMergingHelper) { - if (type instanceof TClass) - AllSuperTypesCollector.collect(type, declMergingHelper).exists[ownedMembers.exists[INJECT.hasAnnotation(it)]] - else - false; - } - - /** - * Generate DI meta info for classes that have injected members, - * or are can be injected and have DI relevant information, e.g. scope annotation. - * Also if type has a super type (injection of inherited members) - */ - def public static boolean isInjectedClass(Type it, DeclMergingHelper declMergingHelper){ - isSingleton || hasInjectedMembers(declMergingHelper) || hasSuperType - } - - def public static boolean isBinder(Type type) { - BINDER.hasAnnotation(type) - } - - def public static boolean isDIComponent(Type type) { - GENERATE_INJECTOR.hasAnnotation(type) - } - - /** - * Checks if diComponent has parent component, that is one specified by the superclass - * or by the inheritance. - */ - def public static boolean hasParentInjector(Type type) { - return WITH_PARENT_INJECTOR.hasAnnotation(type) || - if (type instanceof TClass) { - type.superClassRef !== null; - } else - false; - } - - /** - * @returns {@link Type} of the parent DIComponent. - * @throws {@link RuntimeException} if no parent on provided type. - */ - def public static Type findParentDIC(Type type) { - var TypeRef parent = if (WITH_PARENT_INJECTOR.hasAnnotation(type)) { - (WITH_PARENT_INJECTOR.getOwnedAnnotation(type)?.args.head as TAnnotationTypeRefArgument).typeRef; - } else if (type instanceof TClass) { - type.superClassRef; - } else - null; - - if (parent !== null) { - return parent.declaredType - } - - throw new RuntimeException("no parent on " + type.name); - } - - /** - * returns list of types that are parameters of {@link AnnotationDefinition#USE_BINDER} annotations - * attached to a given type or empty list - */ - def public static List resolveBinders(Type type) { - return USE_BINDER.getAllOwnedAnnotations(type) - .map[args].flatten - .map[(it as TAnnotationTypeRefArgument).typeRef.declaredType].filterNull.toList - } - - /** Returns with {@code true} if one or more members of the given type are annotated with {@code @Inject} annotation. */ - def public static boolean requiresInjection(Type type, DeclMergingHelper declMergingHelper) { - return if (type instanceof TClass) AllSuperTypesCollector.collect(type, declMergingHelper).exists[ownedMembers.exists[INJECT.hasAnnotation(it)]] else false; - } - - /** - * Returns with {@code true} if the type reference argument requires injection. Either the declared type requires injection, - * or the type reference represents an N4 provider, and the dependency of the provider requires injection. Otherwise - * returns with {@code false}. - */ - def public static boolean requiresInjection(TypeRef it, DeclMergingHelper declMergingHelper) { - return declaredType.requiresInjection(declMergingHelper) || (providerType && providedType.requiresInjection(declMergingHelper)) - } - - /** - * Returns with {@code true} if the type reference argument is an N4 provider. Otherwise returns with {@code false}. - * Also returns with {@code false} if the type reference is sub interface of N4 provider or a class which implements - * the N4 provider interface. - */ - def public static boolean isProviderType(TypeRef it) { - null !== it && declaredType === newRuleEnvironment.n4ProviderType; - } - - /** - * Returns with the type most nested dependency if the type reference argument represents and N4 provider. - * Otherwise returns with {@code null}. - */ - def public static getProvidedType(TypeRef it) { - if (!providerType) { - return null; - } - var nestedTypeRef = it; - while (nestedTypeRef.providerType && nestedTypeRef instanceof ParameterizedTypeRef) { - val typeArgs = (nestedTypeRef as ParameterizedTypeRef).declaredTypeArgs.filter(TypeRef); - nestedTypeRef = if (typeArgs.nullOrEmpty) null else typeArgs.head; - } - return nestedTypeRef?.declaredType; - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java new file mode 100644 index 0000000000..f516e206e5 --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2017 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tooling.organizeImports; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import org.eclipse.n4js.N4JSLanguageConstants; +import org.eclipse.n4js.n4JS.DefaultImportSpecifier; +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.ts.types.AbstractNamespace; +import org.eclipse.n4js.ts.types.ElementExportDefinition; +import org.eclipse.n4js.ts.types.ExportDefinition; +import org.eclipse.n4js.ts.types.ModuleExportDefinition; +import org.eclipse.n4js.ts.types.TExportableElement; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.utils.N4JSLanguageUtils; +import org.eclipse.n4js.utils.RecursionGuard; + +import com.google.common.collect.Lists; + +/** + * Utilities for ImportSpecifiers + */ +public class ImportSpecifiersUtil { + + /** + * @return {@link List} of {@link ImportProvidedElement}s describing imported elements + */ + public static List mapToImportProvidedElements( + Collection importSpecifiers) { + + List result = new ArrayList<>(); + for (ImportSpecifier specifier : importSpecifiers) { + if (specifier instanceof NamespaceImportSpecifier) { + result.addAll(namespaceToProvidedElements((NamespaceImportSpecifier) specifier)); + } else if (specifier instanceof NamedImportSpecifier) { + NamedImportSpecifier nis = (NamedImportSpecifier) specifier; + result.add(new ImportProvidedElement(usedName(nis), importedElementName(nis), + nis, N4JSLanguageUtils.isHollowElement(nis.getImportedElement()))); + } + } + + return result; + } + + /** Map all exported elements from namespace target module to the import provided elements. */ + private static List namespaceToProvidedElements(NamespaceImportSpecifier specifier) { + TModule importedModule = importedModule(specifier); + if (importedModule == null || importedModule.eIsProxy()) { + return Collections.emptyList(); + } + + List importProvidedElements = new ArrayList<>(); + // add import provided element for a namespace itself + importProvidedElements.add(new ImportProvidedElement(specifier.getAlias(), + computeNamespaceActualName(specifier), specifier, false)); + + Set localNamesAdded = new HashSet<>(); + collectProvidedElements(importedModule, new RecursionGuard<>(), exportDef -> { + String localName = importedElementName(specifier, exportDef); + // function overloading and declaration merging in .d.ts can lead to multiple elements of same name + // being imported via a single namespace import -> to avoid showing bogus "duplicate import" errors + // in those cases we need to avoid adding more than one ImportProvidedElement in those cases: + // TODO IDE-3604 no longer required for function overloading; should probably be removed once declaration + // merging is supported + if (localNamesAdded.add(localName)) { + importProvidedElements.add( + new ImportProvidedElement(localName, exportDef.getExportedName(), + specifier, N4JSLanguageUtils.isHollowElement(exportDef.getExportedElement()))); + } + }); + + return importProvidedElements; + } + + private static void collectProvidedElements(AbstractNamespace namespace, RecursionGuard guard, + Consumer consumer) { + for (ExportDefinition exportDef : Lists.reverse(namespace.getExportDefinitions())) { + if (exportDef instanceof ElementExportDefinition) { + consumer.accept((ElementExportDefinition) exportDef); + } else if (exportDef instanceof ModuleExportDefinition) { + TModule exportedModule = ((ModuleExportDefinition) exportDef).getExportedModule(); + if (exportedModule != null && !exportedModule.eIsProxy()) { + if (guard.tryNext(exportedModule)) { + collectProvidedElements(exportedModule, guard, consumer); + } + } + } + } + } + + /** + * Computes 'actual' name of the namespace for {@link ImportProvidedElement} entry. If processed namespace refers to + * unresolved module, will return dummy name, otherwise returns artificial name composed of prefix and target module + * qualified name + * + */ + public static String computeNamespaceActualName(NamespaceImportSpecifier specifier) { + if (importedModule(specifier).eIsProxy()) + return ImportProvidedElement.NAMESPACE_PREFIX + specifier.hashCode(); + else + return ImportProvidedElement.NAMESPACE_PREFIX + importedModule(specifier).getQualifiedName().toString(); + } + + /** + * Computes exported name of the element imported by this specifier. + */ + public static String importedElementName(NamedImportSpecifier specifier) { + if (specifier instanceof DefaultImportSpecifier) { + return N4JSLanguageConstants.EXPORT_DEFAULT_NAME; + } + + TExportableElement element = specifier.getImportedElement(); + if (element == null) + return "<" + specifier.getImportedElementAsText() + ">(null)"; + + if (element.eIsProxy()) { + if (specifier.isDeclaredDynamic()) { + return specifier.getImportedElementAsText(); + } + return "<" + specifier.getImportedElementAsText() + ">(proxy)"; + } + + return specifier.getImportedElementAsText(); + } + + /** returns locally used name of element imported via {@link NamedImportSpecifier} */ + public static String usedName(NamedImportSpecifier nis) { + return (nis.getAlias() == null) ? importedElementName(nis) : nis.getAlias(); + } + + /** returns locally used name of element imported via {@link NamespaceImportSpecifier} */ + public static String importedElementName(NamespaceImportSpecifier is, ElementExportDefinition exportDef) { + return is.getAlias() + "." + exportDef.getExportedName(); + } + + /***/ + public static TModule importedModule(ImportSpecifier is) { + return ((ImportDeclaration) is.eContainer()).getModule(); + } + + /** + * Returns true if the module that is target of the import declaration containing provided import specifier is + * invalid (null, proxy, no name). Additionally for {@link NamedImportSpecifier} instances checks if linker failed + * to resolve target (is null, proxy, or has no name) + * + * @param spec + * - the ImportSpecifier to investigate + * @return true import looks broken + */ + public static boolean isBrokenImport(ImportSpecifier spec) { + return isBrokenImport((ImportDeclaration) spec.eContainer(), spec); + } + + /** + * Returns true iff the target module of the given import declaration is invalid (null, proxy, no name). Import + * specifiers are not checked. + */ + public static boolean isBrokenImport(ImportDeclaration decl) { + return isBrokenImport(decl, null); + } + + private static boolean isBrokenImport(ImportDeclaration decl, ImportSpecifier spec) { + TModule module = decl.getModule(); + + // check target module + if (module == null || module.eIsProxy() || isNullOrEmpty(module.getQualifiedName())) { + return true; + } + + // check import specifier + if (spec instanceof NamedImportSpecifier && !spec.isDeclaredDynamic()) { + NamedImportSpecifier nis = (NamedImportSpecifier) spec; + if (nis.eIsProxy() || isNullOrEmpty(nis.getImportedElementAsText())) { + return true; + } + + // check what object that is linked + TExportableElement imported = nis.getImportedElement(); + if (imported == null) { + return true; + } + if (imported.eIsProxy()) { + return true; + } + } + + return false; + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend deleted file mode 100644 index 1fcdef565f..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportSpecifiersUtil.xtend +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2017 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tooling.organizeImports - -import com.google.common.collect.Lists -import java.util.List -import java.util.function.Consumer -import org.eclipse.n4js.N4JSLanguageConstants -import org.eclipse.n4js.n4JS.DefaultImportSpecifier -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.ts.types.AbstractNamespace -import org.eclipse.n4js.ts.types.ElementExportDefinition -import org.eclipse.n4js.ts.types.ModuleExportDefinition -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.N4JSLanguageUtils -import org.eclipse.n4js.utils.RecursionGuard -import org.eclipse.n4js.validation.JavaScriptVariantHelper - -/** - * Utilities for ImportSpecifiers - */ -class ImportSpecifiersUtil { - - /** - * @return {@link List} of {@link ImportProvidedElement}s describing imported elements - */ - public static def List mapToImportProvidedElements( - List importSpecifiers, JavaScriptVariantHelper jsVariantHelper - ) { - return importSpecifiers.map( - specifier | - switch (specifier) { - NamespaceImportSpecifier: - return namespaceToProvidedElements(jsVariantHelper, specifier) - NamedImportSpecifier: - return newArrayList( - new ImportProvidedElement(specifier.usedName, specifier.importedElementName, - specifier as ImportSpecifier, - N4JSLanguageUtils.isHollowElement(specifier.importedElement))) - default: - return emptyList - } - ).flatten.toList - } - - /** Map all exported elements from namespace target module to the import provided elements. */ - private static def List namespaceToProvidedElements(JavaScriptVariantHelper jsVariantHelper, NamespaceImportSpecifier specifier) { - val importedModule = specifier.importedModule; - if (importedModule === null || importedModule.eIsProxy) - return emptyList - - val importProvidedElements = newArrayList - // add import provided element for a namespace itself - importProvidedElements.add(new ImportProvidedElement(specifier.alias, - computeNamespaceActualName(specifier), specifier, false)); - - val localNamesAdded = newHashSet; - collectProvidedElements(importedModule, new RecursionGuard(), [ exportDef | - val localName = specifier.importedElementName(exportDef); - // function overloading and declaration merging in .d.ts can lead to multiple elements of same name - // being imported via a single namespace import -> to avoid showing bogus "duplicate import" errors - // in those cases we need to avoid adding more than one ImportProvidedElement in those cases: - // TODO IDE-3604 no longer required for function overloading; should probably be removed once declaration merging is supported - if (localNamesAdded.add(localName)) { - importProvidedElements.add( - new ImportProvidedElement(localName, exportDef.exportedName, - specifier, N4JSLanguageUtils.isHollowElement(exportDef.exportedElement))); - } - ]); - - return importProvidedElements - } - - private static def void collectProvidedElements(AbstractNamespace namespace, RecursionGuard guard, Consumer consumer) { - for (exportDef : Lists.reverse(namespace.exportDefinitions)) { - if (exportDef instanceof ElementExportDefinition) { - consumer.accept(exportDef); - } else if (exportDef instanceof ModuleExportDefinition) { - val exportedModule = exportDef.exportedModule; - if (exportedModule !== null && !exportedModule.eIsProxy) { - if (guard.tryNext(exportedModule)) { - collectProvidedElements(exportedModule, guard, consumer); - } - } - } - } - } - - /** - * Computes 'actual' name of the namespace for {@link ImportProvidedElement} entry. - * If processed namespace refers to unresolved module, will return dummy name, - * otherwise returns artificial name composed of prefix and target module qualified name - * - */ - public static def String computeNamespaceActualName(NamespaceImportSpecifier specifier) { - if (specifier.importedModule.eIsProxy) - ImportProvidedElement.NAMESPACE_PREFIX + specifier.hashCode - else - ImportProvidedElement.NAMESPACE_PREFIX + specifier.importedModule.qualifiedName.toString - } - - /** - * Computes exported name of the element imported by this specifier. - */ - public static def String importedElementName(NamedImportSpecifier specifier) { - if (specifier instanceof DefaultImportSpecifier) { - return N4JSLanguageConstants.EXPORT_DEFAULT_NAME; - } - - val element = specifier.importedElement - if (element === null) - return "<" + specifier.importedElementAsText + ">(null)" - - if (element.eIsProxy) { - if (specifier.declaredDynamic) { - return specifier.importedElementAsText; - } - return "<" + specifier.importedElementAsText + ">(proxy)" - } - - return specifier.importedElementAsText; - } - - /** returns locally used name of element imported via {@link NamedImportSpecifier} */ - public static def String usedName(NamedImportSpecifier it) { - if (alias === null) importedElementName else alias - } - - /** returns locally used name of element imported via {@link NamespaceImportSpecifier} */ - public static def String importedElementName(NamespaceImportSpecifier is, ElementExportDefinition exportDef) { - is.alias + "." + exportDef.exportedName - } - - public static def TModule importedModule(ImportSpecifier it) { - (eContainer as ImportDeclaration).module - } - - /** - * Returns true if the module that is target of the import declaration containing provided import specifier is invalid (null, proxy, no name). - * Additionally for {@link NamedImportSpecifier} instances checks if linker failed to resolve target (is null, proxy, or has no name) - * - * @param spec - the ImportSpecifier to investigate - * @return true import looks broken - * */ - public static def boolean isBrokenImport(ImportSpecifier spec) { - return isBrokenImport(spec.eContainer as ImportDeclaration, spec); - } - - /** - * Returns true iff the target module of the given import declaration is invalid (null, proxy, no name). - * Import specifiers are not checked. - */ - public static def boolean isBrokenImport(ImportDeclaration decl) { - return isBrokenImport(decl, null); - } - - private static def boolean isBrokenImport(ImportDeclaration decl, ImportSpecifier spec) { - val module = decl.module; - - // check target module - if (module === null || module.eIsProxy || module.qualifiedName.isNullOrEmpty) - return true - - // check import specifier - if (spec instanceof NamedImportSpecifier && !spec.declaredDynamic) { - val nis = spec as NamedImportSpecifier; - if (nis === null || nis.eIsProxy || nis.importedElementAsText.isNullOrEmpty) - return true - - // check what object that is linked - val imported = nis.importedElement - if (imported === null) - return true - if (imported.eIsProxy) - return true - } - - return false - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java new file mode 100644 index 0000000000..d88111b6fa --- /dev/null +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.java @@ -0,0 +1,318 @@ +/** + * Copyright (c) 2016 NumberFour AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * NumberFour AG - Initial API and implementation + */ +package org.eclipse.n4js.tooling.organizeImports; + +import static org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.computeNamespaceActualName; +import static org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.isBrokenImport; +import static org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.mapToImportProvidedElements; +import static org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver.usedDependenciesTypeRefs; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.exists; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.filter; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.findFirst; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.flatten; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.forEach; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.head; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.map; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.tail; +import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.eclipse.n4js.n4JS.ImportDeclaration; +import org.eclipse.n4js.n4JS.ImportSpecifier; +import org.eclipse.n4js.n4JS.N4JSPackage; +import org.eclipse.n4js.n4JS.NamedImportSpecifier; +import org.eclipse.n4js.n4JS.NamespaceImportSpecifier; +import org.eclipse.n4js.n4JS.Script; +import org.eclipse.n4js.postprocessing.ASTMetaInfoCache; +import org.eclipse.n4js.resource.N4JSResource; +import org.eclipse.n4js.ts.types.TModule; +import org.eclipse.n4js.validation.IssueCodes; +import org.eclipse.xtext.xbase.lib.Pair; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; + +/** + * Analyzes all imports in a script. Builds up a data structure of {@link RecordingImportState} to capture the findings. + */ +public class ImportStateCalculator { + + /** + * Algorithm to check the Model for Issues with Imports. + * + * @returns {@link RecordingImportState} + */ + public RecordingImportState calculateImportstate(Script script) { + RecordingImportState reg = new RecordingImportState(); + + ASTMetaInfoCache astMetaInfoCache = ((N4JSResource) script.eResource()).getASTMetaInfoCacheVerifyContext(); + + // Calculate Available + Iterable importDeclarationsALL = filter(script.getScriptElements(), ImportDeclaration.class); + + registerDuplicatedImoprtDeclarationsFrom(reg, importDeclarationsALL); + + List importSpecifiersUnAnalyzed = toList( + flatten(map(filter(importDeclarationsALL, it -> !reg.isDuplicatingImportDeclaration(it)), + decl -> decl.getImportSpecifiers()))); + + // markDuplicatingSpecifiersAsUnused(importSpecifiersUnAnalyzed) + + // collect all unused if stable + registerUnusedAndBrokenImports(reg, importSpecifiersUnAnalyzed, astMetaInfoCache); + + List importProvidedElements = mapToImportProvidedElements( + toList(filter(importSpecifiersUnAnalyzed, it -> !(reg.brokenImports.contains(it))))); + + // refactor into specific types, those are essentially Maps holding elements in insertion order (keys and + // values) + List>> lN2IPE = new ArrayList<>(); + List>> lM2IPE = new ArrayList<>(); + + // TODO refactor this, those composed collections should be encapsulated as specific types with proper get/set + // methods + for (ImportProvidedElement ipe : importProvidedElements) { + Pair> pN2IPE = findFirst(lN2IPE, + p -> Objects.equals(p.getKey(), ipe.getCollisionUniqueName())); + + if (pN2IPE != null) { + pN2IPE.getValue().add(ipe); + } else { + List al = new ArrayList<>(); + al.add(ipe); + lN2IPE.add(Pair.of(ipe.getCollisionUniqueName(), al)); + } + Pair> pM2IPE = findFirst(lM2IPE, + p -> Objects.equals(p.getKey(), ipe.getImportedModule())); + if (pM2IPE != null) { + pM2IPE.getValue().add(ipe); + } else { + List al = new ArrayList<>(); + al.add(ipe); + lM2IPE.add(Pair.of(ipe.getImportedModule(), al)); + } + } + + registerUsedImportsLocalNamesCollisions(reg, lN2IPE); + + registerUsedImportsDuplicatedImportedElements(reg, lM2IPE); + + reg.registerAllUsedTypeNameToSpecifierTuples(importProvidedElements); + + // usages in script: + List externalDep = usedDependenciesTypeRefs(script); + + // mark used imports as seen in externalDep: + for (ScriptDependency scriptDep : externalDep) { + TModule mod = scriptDep.dependencyModule; + Pair> pM2IPE = findFirst(lM2IPE, p -> p.getKey() == mod); + if (pM2IPE != null) { + forEach(filter(pM2IPE.getValue(), ipe -> Objects.equals(ipe.getExportedName(), scriptDep.actualName) + && Objects.equals(ipe.getLocalName(), scriptDep.localName)), it -> it.markUsed()); + } + } + + /* + * TODO review ambiguous imports looks like reference to type that is ambiguously imported can happen only if + * there are errors in the import declaratiosn, so should ignore those references and resolve issues in the + * imports only? Or can this information be used to resolve those issues in smarter way? + */ + // localname2importprovider.markAmbigousImports(script) + + return reg; + } + + /** + * Registers conflicting or duplicate (based on imported elements) imports in the provided + * {@link RecordingImportState} + */ + private void registerUsedImportsDuplicatedImportedElements(RecordingImportState reg, + List>> module2imported) { + + for (Pair> pair : module2imported) { + List fromMod = pair.getValue(); + + // find duplicates in actual name, report them as duplicateImport + ArrayListMultimap name2Import = ArrayListMultimap.create(); + for (ImportProvidedElement ipe : fromMod) { + name2Import.put(ipe.getDuplicateImportUniqueName(), ipe); + } + + for (String name : name2Import.keySet()) { + List v = name2Import.get(name); + String actName = v.get(0).getExportedName(); + List x = toList(filter(v, internalIPE -> { + // filter out ImportProvidedElements that reflect Namespace element itself + ImportSpecifier specifier = internalIPE.getImportSpecifier(); + if (specifier instanceof NamespaceImportSpecifier) { + return !Objects.equals(internalIPE.getExportedName(), + computeNamespaceActualName((NamespaceImportSpecifier) specifier)); + } else { + return true; + } + })); + if (x.size() > 1) { + reg.registerDuplicateImportsOfSameElement(actName, pair.getKey(), x); + } + } + } + } + + /** + * Registers conflicting or duplicate (based on local name checks) imports in the provided + * {@link RecordingImportState} + */ + private void registerUsedImportsLocalNamesCollisions(RecordingImportState reg, + List>> localname2importprovider) { + for (Pair> pair : localname2importprovider) { + if (pair.getValue().size() > 1) { + reg.registerLocalNameCollision(pair.getKey(), pair.getValue()); + } + } + } + + /** + * analyzes provided {@link ImportDeclaration}s, if it finds *exact* duplicates, adds them to the + * {@link RecordingImportState#duplicatedImportDeclarations} + */ + private void registerDuplicatedImoprtDeclarationsFrom(RecordingImportState reg, + Iterable importDeclarations) { + Multimap nsisImports = LinkedHashMultimap.create(); + Multimap nisImports = LinkedHashMultimap.create(); + for (ImportDeclaration id : importDeclarations) { + if (findFirst(id.getImportSpecifiers(), is -> is instanceof NamespaceImportSpecifier) != null) { + nsisImports.put(id.getModule(), id); + } + if (findFirst(id.getImportSpecifiers(), is -> is instanceof NamedImportSpecifier) != null) { + nisImports.put(id.getModule(), id); + } + } + + for (TModule module : nsisImports.keySet()) { + registerImportDeclarationsWithNamespaceImportsForModule(nsisImports.get(module), reg); + } + + for (TModule module : nisImports.keySet()) { + registerImportDeclarationsWithNamedImportsForModule(nisImports.get(module), reg); + } + + // importDeclarations + // .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamespaceImportSpecifier] !== null] + // .groupBy[module] + // .forEach[module, impDecls| + // registerImportDeclarationsWithNamespaceImportsForModule(impDecls, reg) + // ] + // + // for (ImportDeclaration id: importDeclarations ) { + // + // } + // importDeclarations + // .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamedImportSpecifier] !== null] + // .groupBy[module] + // .forEach[module, impDecls| + // registerImportDeclarationsWithNamedImportsForModule(impDecls, reg) + // ] + } + + private void registerImportDeclarationsWithNamespaceImportsForModule( + Collection importDeclarations, + RecordingImportState reg) { + if (importDeclarations.size() < 2) { + return; + } + + List duplicates = new ArrayList<>(); + ImportDeclaration firstDeclaration = importDeclarations.iterator().next(); + String firstNamespaceName = head(filter(firstDeclaration.getImportSpecifiers(), NamespaceImportSpecifier.class)) + .getAlias(); + for (ImportDeclaration importDeclaration : tail(importDeclarations)) { + String followingNamespaceName = head( + filter(importDeclaration.getImportSpecifiers(), NamespaceImportSpecifier.class)).getAlias(); + if (Objects.equals(firstNamespaceName, followingNamespaceName)) { + duplicates.add(importDeclaration); + } + } + + if (!duplicates.isEmpty()) { + reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); + } + } + + private void registerImportDeclarationsWithNamedImportsForModule(Collection importDeclarations, + RecordingImportState reg) { + if (importDeclarations.size() < 2) { + return; + } + + List duplicates = new ArrayList<>(); + ImportDeclaration firstDeclaration = importDeclarations.iterator().next(); + List firstDeclarationSpecifiers = toList( + filter(firstDeclaration.getImportSpecifiers(), NamedImportSpecifier.class)); + for (ImportDeclaration importDeclaration : tail(importDeclarations)) { + List followingDeclarationSpecifiers = toList( + filter(importDeclaration.getImportSpecifiers(), NamedImportSpecifier.class)); + if ((!firstDeclarationSpecifiers.isEmpty()) + && firstDeclarationSpecifiers.size() == followingDeclarationSpecifiers.size()) { + if (allFollowingMatchByNameAndAlias(firstDeclarationSpecifiers, followingDeclarationSpecifiers)) { + duplicates.add(importDeclaration); + } + } + } + + if (!duplicates.isEmpty()) { + reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); + } + } + + private boolean allFollowingMatchByNameAndAlias(Iterable firstDeclarationSpecifiers, + Iterable followingDeclarationSpecifiers) { + + for (NamedImportSpecifier namedImportSpecifier : firstDeclarationSpecifiers) { + if (!exists(followingDeclarationSpecifiers, + otherNamedImportSpecifier -> Objects.equals(namedImportSpecifier.getAlias(), + otherNamedImportSpecifier.getAlias()) + && Objects.equals(namedImportSpecifier.getImportedElement().getName(), + otherNamedImportSpecifier.getImportedElement().getName()))) { + return false; + } + } + return true; + } + + /** + * Registers unused or broken (missing or unresolved imported module) import specifiers in the provided + * {@link RecordingImportState} + */ + private void registerUnusedAndBrokenImports(RecordingImportState reg, List importSpecifiers, + ASTMetaInfoCache astMetaInfoCache) { + for (ImportSpecifier is : importSpecifiers) { + // avoid duplicate error messages + if (!isNamedImportOfNonExportedElement(is, astMetaInfoCache) && !is.isFlaggedUsedInCode()) { + reg.registerUnusedImport(is); + if (isBrokenImport(is)) { + reg.registerBrokenImport(is); + } + } + } + } + + private static boolean isNamedImportOfNonExportedElement(ImportSpecifier importSpec, + ASTMetaInfoCache astMetaInfoCache) { + return astMetaInfoCache + .getLinkingIssueCodes(importSpec, N4JSPackage.Literals.NAMED_IMPORT_SPECIFIER__IMPORTED_ELEMENT) + .contains(IssueCodes.IMP_NOT_EXPORTED.name()); + } +} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend deleted file mode 100644 index 85aa1e7a16..0000000000 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/tooling/organizeImports/ImportStateCalculator.xtend +++ /dev/null @@ -1,243 +0,0 @@ -/** - * Copyright (c) 2016 NumberFour AG. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * NumberFour AG - Initial API and implementation - */ -package org.eclipse.n4js.tooling.organizeImports - -import com.google.common.collect.ArrayListMultimap -import com.google.inject.Inject -import java.util.List -import org.eclipse.n4js.n4JS.ImportDeclaration -import org.eclipse.n4js.n4JS.ImportSpecifier -import org.eclipse.n4js.n4JS.N4JSPackage -import org.eclipse.n4js.n4JS.NamedImportSpecifier -import org.eclipse.n4js.n4JS.NamespaceImportSpecifier -import org.eclipse.n4js.n4JS.Script -import org.eclipse.n4js.postprocessing.ASTMetaInfoCache -import org.eclipse.n4js.resource.N4JSResource -import org.eclipse.n4js.ts.types.TModule -import org.eclipse.n4js.utils.Log -import org.eclipse.n4js.validation.IssueCodes -import org.eclipse.n4js.validation.JavaScriptVariantHelper - -import static extension org.eclipse.n4js.tooling.organizeImports.ImportSpecifiersUtil.* -import static extension org.eclipse.n4js.tooling.organizeImports.ScriptDependencyResolver.* - -/** - * Analyzes all imports in a script. Builds up a data structure of {@link RecordingImportState} to capture the findings. - */ -@Log -class ImportStateCalculator { - - @Inject - private JavaScriptVariantHelper jsVariantHelper; - - /** - * Algorithm to check the Model for Issues with Imports. - * @returns {@link RecordingImportState} - */ - public def RecordingImportState calculateImportstate(Script script) { - val reg = new RecordingImportState(); - - val astMetaInfoCache = (script.eResource as N4JSResource).getASTMetaInfoCacheVerifyContext(); - - // Calculate Available - val importDeclarationsALL = script.scriptElements.filter(ImportDeclaration) - - reg.registerDuplicatedImoprtDeclarationsFrom(importDeclarationsALL) - - val importSpecifiersUnAnalyzed = importDeclarationsALL.filter[!reg.isDuplicatingImportDeclaration(it)].map[importSpecifiers].flatten.toList - -// markDuplicatingSpecifiersAsUnused(importSpecifiersUnAnalyzed) - - // collect all unused if stable - reg.registerUnusedAndBrokenImports(importSpecifiersUnAnalyzed, astMetaInfoCache) - - val List importProvidedElements = importSpecifiersUnAnalyzed.filter[!(reg.brokenImports.contains(it))].toList.mapToImportProvidedElements(jsVariantHelper) - - //refactor into specific types, those are essentially Maps holding elements in insertion order (keys and values) - val List>> lN2IPE = newArrayList() - val List>> lM2IPE = newArrayList() - - //TODO refactor this, those composed collections should be encapsulated as specific types with proper get/set methods - for (ipe : importProvidedElements) { - val pN2IPE = lN2IPE.findFirst[it.key == ipe.getCollisionUniqueName()]; - if(pN2IPE !== null){ - pN2IPE.value.add(ipe) - }else{ - lN2IPE.add(ipe.getCollisionUniqueName() -> newArrayList(ipe)) - } - val pM2IPE = lM2IPE.findFirst[it.key == ipe.importedModule]; - if(pM2IPE !== null){ - pM2IPE.value.add(ipe) - }else{ - lM2IPE.add(ipe.importedModule -> newArrayList(ipe)) - } - } - - reg.registerUsedImportsLocalNamesCollisions(lN2IPE) - - reg.registerUsedImportsDuplicatedImportedElements(lM2IPE) - - reg.registerAllUsedTypeNameToSpecifierTuples(importProvidedElements) - - - // usages in script: - val externalDep = script.usedDependenciesTypeRefs - - // mark used imports as seen in externalDep: - for( scriptDep : externalDep ) { - val mod = scriptDep.dependencyModule - val pM2IPE = lM2IPE.findFirst[it.key == mod] - if(pM2IPE !== null){ - pM2IPE.value.filter[it.exportedName == scriptDep.actualName && it.getLocalName() == scriptDep.localName ].forEach[ it.markUsed]; - } - } - -/*TODO review ambiguous imports - * looks like reference to type that is ambiguously imported can happen only if there are errors in the import declaratiosn, - * so should ignore those references and resolve issues in the imports only? - * Or can this information be used to resolve those issues in smarter way? - */ -// localname2importprovider.markAmbigousImports(script) - - - return reg; - } - - /** - * Registers conflicting or duplicate (based on imported elements) imports in the provided {@link RecordingImportState} - */ - private def registerUsedImportsDuplicatedImportedElements(RecordingImportState reg, List>> module2imported) { - for (pair : module2imported) { - val fromMod = pair.value - - // find duplicates in actual name, report them as duplicateImport - val name2Import = ArrayListMultimap.create - for (ipe : fromMod) - name2Import.put(ipe.getDuplicateImportUniqueName, ipe) - - for (name : name2Import.keySet) { - val v = name2Import.get(name).toList - val actName = v.head.exportedName; - val x = v.filter[internalIPE| - // filter out ImportProvidedElements that reflect Namespace element itself - val specifier = internalIPE.importSpecifier; - if(specifier instanceof NamespaceImportSpecifier){ - internalIPE.exportedName != computeNamespaceActualName(specifier) - }else{ - true - } - ].toList - if (x.size > 1) - reg.registerDuplicateImportsOfSameElement(actName, pair.key, x) - } - } - } - - /** - * Registers conflicting or duplicate (based on local name checks) imports in the provided {@link RecordingImportState} - */ - private def registerUsedImportsLocalNamesCollisions(RecordingImportState reg, List>> localname2importprovider) { - for(pair : localname2importprovider){ - if(pair.value.size>1){ - reg.registerLocalNameCollision(pair.key, pair.value) - } - } - } - - /** - * analyzes provided {@link ImportDeclaration}s, if it finds *exact* duplicates, adds them to the {@link RecordingImportState#duplicatedImportDeclarations} - */ - private def void registerDuplicatedImoprtDeclarationsFrom(RecordingImportState reg, Iterable importDeclarations) { - - importDeclarations - .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamespaceImportSpecifier] !== null] - .groupBy[module] - .forEach[module, impDecls| - registerImportDeclarationsWithNamespaceImportsForModule(impDecls, reg) - ] - - importDeclarations - .filter[id| id.importSpecifiers.findFirst[is| is instanceof NamedImportSpecifier] !== null] - .groupBy[module] - .forEach[module, impDecls| - registerImportDeclarationsWithNamedImportsForModule(impDecls, reg) - ] - } - - private def void registerImportDeclarationsWithNamespaceImportsForModule(List importDeclarations, - RecordingImportState reg) { - if (importDeclarations.size < 2) - return; - - val duplicates = newArrayList - val firstDeclaration = importDeclarations.head - val firstNamespaceName = (firstDeclaration.importSpecifiers.filter(NamespaceImportSpecifier).head).alias - importDeclarations.tail.forEach [ importDeclaration | - val followingNamespaceName = importDeclaration.importSpecifiers.filter(NamespaceImportSpecifier).head.alias - if (firstNamespaceName == followingNamespaceName) - duplicates.add(importDeclaration) - ] - - if (!duplicates.empty) - reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); - } - - private def void registerImportDeclarationsWithNamedImportsForModule(List importDeclarations, - RecordingImportState reg) { - if (importDeclarations.size < 2) - return; - - val duplicates = newArrayList - val firstDeclaration = importDeclarations.head - val firstDeclarationSpecifiers = firstDeclaration.importSpecifiers.filter(NamedImportSpecifier) - importDeclarations.tail.forEach [ importDeclaration | - val followingDeclarationSpecifiers = importDeclaration.importSpecifiers.filter(NamedImportSpecifier) - if ((!firstDeclarationSpecifiers.empty) && - firstDeclarationSpecifiers.size === followingDeclarationSpecifiers.size) { - if (firstDeclarationSpecifiers.allFollowingMatchByNameAndAlias(followingDeclarationSpecifiers)) - duplicates.add(importDeclaration) - } - ] - - if (!duplicates.empty) - reg.registerDuplicatesOfImportDeclaration(firstDeclaration, duplicates); - } - - private def boolean allFollowingMatchByNameAndAlias(Iterable firstDeclarationSpecifiers, - Iterable followingDeclarationSpecifiers) { - - firstDeclarationSpecifiers.forall [ namedImportSpecifier | - followingDeclarationSpecifiers.exists [ otherNamedImportSpecifier | - namedImportSpecifier.alias == otherNamedImportSpecifier.alias && - namedImportSpecifier.importedElement.name == otherNamedImportSpecifier.importedElement.name - ] - ] - } - - /** - * Registers unused or broken (missing or unresolved imported module) import specifiers in the provided {@link RecordingImportState} - */ - private def void registerUnusedAndBrokenImports(RecordingImportState reg, List importSpecifiers, ASTMetaInfoCache astMetaInfoCache) { - for (is : importSpecifiers) { - if (!isNamedImportOfNonExportedElement(is, astMetaInfoCache) // avoid duplicate error messages - && !is.isFlaggedUsedInCode) { - reg.registerUnusedImport(is); - if (is.isBrokenImport) - reg.registerBrokenImport(is); - } - } - } - - private static def boolean isNamedImportOfNonExportedElement(ImportSpecifier importSpec, ASTMetaInfoCache astMetaInfoCache) { - return astMetaInfoCache.getLinkingIssueCodes(importSpec, N4JSPackage.Literals.NAMED_IMPORT_SPECIFIER__IMPORTED_ELEMENT) - .contains(IssueCodes.IMP_NOT_EXPORTED.name()); - } -} diff --git a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend index b5204fbda8..5a2b8422b5 100644 --- a/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend +++ b/plugins/org.eclipse.n4js/src/org/eclipse/n4js/validation/validators/N4JSImportValidator.xtend @@ -340,7 +340,7 @@ class N4JSImportValidator extends AbstractN4JSDeclarativeValidator { private def handleNotImportedTypeRefs(Script script, List specifiersWithIssues, Map eObjectToIssueCode) { - val importedProvidedElementsWithIssuesByModule = specifiersWithIssues.mapToImportProvidedElements(jsVariantHelper).groupBy [ + val importedProvidedElementsWithIssuesByModule = mapToImportProvidedElements(specifiersWithIssues).groupBy [ importedModule ] val potentiallyAffectedTypeRefs = script.eAllContents.filter(ParameterizedTypeRef).filter [