diff --git a/CHANGELOG.md b/CHANGELOG.md index 2106ba1d..9e9bab76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Ограничения на использование экспортных процедур и функций в модулях команд и форм - Вызов "Заблокировать()" находится вне попытки - Для проверок dynamic-access-method-not-found и property-return-type добавлена возможность исключения по типам (COM-Объекты) +- Проверка типов invocation-parameter-type-intersect проверяет типы элементов коллекций: Массив, Соотвествие, СписокЗначений #### Запросы diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/strict/check/InvocationParamIntersectionCheck.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/strict/check/InvocationParamIntersectionCheck.java index d4da86ff..6d0ff101 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/strict/check/InvocationParamIntersectionCheck.java +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/strict/check/InvocationParamIntersectionCheck.java @@ -21,7 +21,9 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -36,11 +38,14 @@ import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.naming.IQualifiedNameConverter; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.util.Triple; +import org.eclipse.xtext.util.Tuples; import com._1c.g5.v8.dt.bsl.common.IBslPreferences; import com._1c.g5.v8.dt.bsl.documentation.comment.BslCommentUtils; import com._1c.g5.v8.dt.bsl.documentation.comment.BslDocumentationComment; import com._1c.g5.v8.dt.bsl.model.BslPackage; +import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess; import com._1c.g5.v8.dt.bsl.model.EmptyExpression; import com._1c.g5.v8.dt.bsl.model.Expression; import com._1c.g5.v8.dt.bsl.model.FeatureAccess; @@ -61,6 +66,8 @@ import com._1c.g5.v8.dt.mcore.McorePackage; import com._1c.g5.v8.dt.mcore.NamedElement; import com._1c.g5.v8.dt.mcore.ParamSet; import com._1c.g5.v8.dt.mcore.Parameter; +import com._1c.g5.v8.dt.mcore.Property; +import com._1c.g5.v8.dt.mcore.Type; import com._1c.g5.v8.dt.mcore.TypeItem; import com._1c.g5.v8.dt.mcore.util.Environments; import com._1c.g5.v8.dt.mcore.util.McoreUtil; @@ -81,11 +88,43 @@ import com.google.inject.Inject; public class InvocationParamIntersectionCheck extends AbstractTypeCheck { + private static final String METHOD_INSERT = "Insert"; //$NON-NLS-1$ + + private static final String MAP_GET = "Get"; //$NON-NLS-1$ + + private static final String MAP_DELETE = "Delete"; //$NON-NLS-1$ + + private static final String MAP_KEY = "Key"; //$NON-NLS-1$ + + private static final String MAP_VALUE = "Value"; //$NON-NLS-1$ private static final String CHECK_ID = "invocation-parameter-type-intersect"; //$NON-NLS-1$ private static final String PARAM_ALLOW_DYNAMIC_TYPES_CHECK = "allowDynamicTypesCheck"; //$NON-NLS-1$ + //@formatter:off + private static final Map>> COLLECTION_ADD_METHODS = Map.of( + "Add", Map.of(IEObjectTypeNames.ARRAY, Set.of(0), //$NON-NLS-1$ + IEObjectTypeNames.VALUE_LIST, Set.of(0)), + METHOD_INSERT, Map.of( + IEObjectTypeNames.ARRAY, Set.of(1), + IEObjectTypeNames.VALUE_LIST, Set.of(1), + IEObjectTypeNames.MAP, Set.of(0, 1)), + "Set", Map.of( //$NON-NLS-1$ + IEObjectTypeNames.ARRAY, Set.of(1)), + MAP_GET, Map.of(IEObjectTypeNames.MAP, Set.of(0)), + MAP_DELETE, Map.of(IEObjectTypeNames.MAP, Set.of(0)), + "Find", Map.of(IEObjectTypeNames.ARRAY, Set.of(0)) //$NON-NLS-1$ + ); + + private static final Map> MAP_KEY_VALUE_TYPES = Map.of( + METHOD_INSERT, Map.of(0, MAP_KEY, 1, MAP_VALUE), + MAP_GET, Map.of(0, MAP_KEY), + MAP_DELETE, Map.of(0, MAP_KEY) + ); + + //@formatter:on + private final IV8ProjectManager v8ProjectManager; private final ExportMethodTypeProvider exportMethodTypeProvider; @@ -184,6 +223,13 @@ public class InvocationParamIntersectionCheck Environments actualEnvs = getActualEnvironments(inv); + // This allows to check collection item type + Triple, Collection, Boolean> collectionItemContext = + getCollectionItemContext(inv, method, actualEnvs); + Collection collectionItemTypes = collectionItemContext.getFirst(); + Collection parameterNumbers = collectionItemContext.getSecond(); + boolean isMap = collectionItemContext.getThird(); + for (int i = 0; i < inv.getParams().size(); i++) { if (monitor.isCanceled()) @@ -196,10 +242,12 @@ public class InvocationParamIntersectionCheck boolean isUndefined = param == null || param instanceof UndefinedLiteral || param instanceof EmptyExpression || isUndefinedType(sorceTypes); - Collection targetTypes = Collections.emptyList(); - boolean isIntersect = false; + Collection targetTypes = + getDefaultTargetOrCollectionItemTypes(method, collectionItemTypes, parameterNumbers, isMap, i, inv); + boolean isIntersect = !targetTypes.isEmpty() && intersectTypeItem(targetTypes, sorceTypes, inv); Parameter parameter = null; - for (Iterator iterator = paramSets.iterator(); iterator.hasNext();) + for (Iterator iterator = paramSets.iterator(); !isIntersect && targetTypes.isEmpty() + && iterator.hasNext();) { ParamSet paramSet = iterator.next(); @@ -252,10 +300,6 @@ public class InvocationParamIntersectionCheck } isIntersect = intersectTypeItem(targetTypes, sorceTypes, inv); - if (isIntersect) - { - break; - } } if (!isIntersect && !targetTypes.isEmpty()) @@ -265,6 +309,79 @@ public class InvocationParamIntersectionCheck } } + private Triple, Collection, Boolean> getCollectionItemContext(Invocation inv, + com._1c.g5.v8.dt.mcore.Method method, Environments actualEnvs) + { + Collection collectionItemTypes = new ArrayList<>(); + Collection parameterNumbers = Collections.emptyList(); + boolean isMap = false; + + if (!(method instanceof SourceObjectLinkProvider) && inv.getMethodAccess() instanceof DynamicFeatureAccess) + { + Map> typesAndParams = COLLECTION_ADD_METHODS.get(method.getName()); + if (typesAndParams != null) + { + TypeItem collectionType = EcoreUtil2.getContainerOfType(method, TypeItem.class); + String typeName = McoreUtil.getTypeName(collectionType); + if (typeName != null && typesAndParams.containsKey(typeName)) + { + List types = typeComputer + .computeTypes(((DynamicFeatureAccess)inv.getMethodAccess()).getSource(), actualEnvs); + for (TypeItem type : types) + { + type = (TypeItem)EcoreUtil.resolve(type, inv); + if (type instanceof Type && typeName.equals(McoreUtil.getTypeName(type))) + { + collectionItemTypes.addAll(((Type)type).getCollectionElementTypes().allTypes()); + isMap = IEObjectTypeNames.MAP.equals(typeName); + parameterNumbers = typesAndParams.get(typeName); + } + } + // Remove Arbitrary type which do not need to check + for (Iterator iterator = collectionItemTypes.iterator(); iterator.hasNext();) + { + TypeItem typeItem = iterator.next(); + if (IEObjectTypeNames.ARBITRARY.equals(McoreUtil.getTypeName(typeItem))) + { + iterator.remove(); + } + } + } + } + } + return Tuples.create(collectionItemTypes, parameterNumbers, isMap); + } + + private Collection getDefaultTargetOrCollectionItemTypes(com._1c.g5.v8.dt.mcore.Method method, + Collection collectionItemTypes, Collection parameterNumbers, boolean isMap, + int parameterNumber, EObject context) + { + Collection targetTypes = Collections.emptyList(); + if (!collectionItemTypes.isEmpty() && parameterNumbers.contains(parameterNumber)) + { + // use collection item types + if (isMap) + { + String name = MAP_KEY_VALUE_TYPES.get(method.getName()).get(parameterNumber); + Optional property = + dynamicFeatureAccessComputer.getAllProperties(collectionItemTypes, context.eResource()) + .stream() + .flatMap(p -> p.getFirst().stream()) + .filter(p -> name.equals(p.getName())) + .findFirst(); + if (property.isPresent()) + { + targetTypes = property.get().getTypes(); + } + } + else + { + targetTypes = collectionItemTypes; + } + } + return targetTypes; + } + private boolean isUndefinedType(List types) { if (types.size() == 1) @@ -313,7 +430,7 @@ public class InvocationParamIntersectionCheck FormalParam formalParam = targetParams.get(i); String paramName = formalParam.getName(); - Collection targetTypes = Collections.emptyList(); + Collection targetTypes = null; if (docComment.isPresent() && docComment.get().getParametersSection().getParameterByName(paramName) != null) { // if parameter declared in doc-comment then check only declared types diff --git a/tests/com.e1c.v8codestyle.bsl.itests/resources/strict/invocation-parameter-type-intersect-collection-item.bsl b/tests/com.e1c.v8codestyle.bsl.itests/resources/strict/invocation-parameter-type-intersect-collection-item.bsl new file mode 100644 index 00000000..03d6918f --- /dev/null +++ b/tests/com.e1c.v8codestyle.bsl.itests/resources/strict/invocation-parameter-type-intersect-collection-item.bsl @@ -0,0 +1,31 @@ +// @strict-types + +// Parameters: +// MapParamenter - Map of KeyAndValue: +// * Key - Number - +// * Value - Number - +Procedure NonComplaint(MapParamenter) Export + + Array = new Array; // Array of Number + Array.Add(""); + + MapParamenter.Insert("", + False); + +EndProcedure + + +// Parameters: +// MapParamenter - Map of KeyAndValue: +// * Key - Number - +// * Value - Number - +Procedure Complaint(MapParamenter) Export + + Array = new Array; // Array of Number + Array.Add(10); + + MapParamenter.Insert(10, + 10); + +EndProcedure + diff --git a/tests/com.e1c.v8codestyle.bsl.itests/resources/strict/invocation-parameter-type-intersect-local-doc-comment.bsl b/tests/com.e1c.v8codestyle.bsl.itests/resources/strict/invocation-parameter-type-intersect-local-doc-comment.bsl new file mode 100644 index 00000000..5e591062 --- /dev/null +++ b/tests/com.e1c.v8codestyle.bsl.itests/resources/strict/invocation-parameter-type-intersect-local-doc-comment.bsl @@ -0,0 +1,17 @@ +// @strict-types + +Procedure NonComplaint() Export + + Complaint("1"); + +EndProcedure + + +// Parameters: +// Strings - Array +Procedure Complaint(Strings) + + Complaint(Strings); + +EndProcedure + diff --git a/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/strict/check/itests/CommonModuleStrictTypesTest.java b/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/strict/check/itests/CommonModuleStrictTypesTest.java index daf5dd06..883e3de3 100644 --- a/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/strict/check/itests/CommonModuleStrictTypesTest.java +++ b/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/strict/check/itests/CommonModuleStrictTypesTest.java @@ -407,6 +407,34 @@ public class CommonModuleStrictTypesTest } + /** + * Test of {@link InvocationParamIntersectionCheck} that invokable method parameter type intersects + * with caller type for collections with typed items. + * + * @throws Exception the exception + */ + @Test + public void testInvocationParamIntersectionCollectionItemCheck() throws Exception + { + + String checkId = "invocation-parameter-type-intersect"; + String resouceName = "invocation-parameter-type-intersect-collection-item"; + + Module module = updateAndGetModule(resouceName); + + List markers = getMarters(checkId, module); + + assertEquals(3, markers.size()); + + Marker marker = markers.get(0); + assertEquals("10", marker.getExtraInfo().get(IExtraInfoKeys.TEXT_EXTRA_INFO_LINE_KEY)); + marker = markers.get(1); + assertEquals("13", marker.getExtraInfo().get(IExtraInfoKeys.TEXT_EXTRA_INFO_LINE_KEY)); + marker = markers.get(2); + assertEquals("12", marker.getExtraInfo().get(IExtraInfoKeys.TEXT_EXTRA_INFO_LINE_KEY)); + + } + /** * Test of {@link InvocationParamIntersectionCheck} that invokable method parameter type intersects * with caller type, and skip checking if method has default value parameters. @@ -431,6 +459,30 @@ public class CommonModuleStrictTypesTest } + /** + * Test of {@link InvocationParamIntersectionCheck} that invokable method parameter type intersects + * with caller type that is local method with documentation comment. + * + * @throws Exception the exception + */ + @Test + public void testInvocationParamIntersectionCheckLocalDocComment() throws Exception + { + + String checkId = "invocation-parameter-type-intersect"; + String resouceName = "invocation-parameter-type-intersect-local-doc-comment"; + + Module module = updateAndGetModule(resouceName); + + List markers = getMarters(checkId, module); + + assertEquals(1, markers.size()); + + Marker marker = markers.get(0); + assertEquals("5", marker.getExtraInfo().get(IExtraInfoKeys.TEXT_EXTRA_INFO_LINE_KEY)); + + } + private IDtProject getProject() { return dtProject; @@ -446,9 +498,9 @@ public class CommonModuleStrictTypesTest return markers; } - private Module updateAndGetModule(String checkId) throws CoreException, IOException + private Module updateAndGetModule(String resourceName) throws CoreException, IOException { - try (InputStream in = getClass().getResourceAsStream(FOLDER + checkId + ".bsl")) + try (InputStream in = getClass().getResourceAsStream(FOLDER + resourceName + ".bsl")) { IFile file = getProject().getWorkspaceProject().getFile(COMMON_MODULE_FILE_NAME); file.setContents(in, true, true, new NullProgressMonitor());