1
0
mirror of https://github.com/1C-Company/v8-code-style.git synced 2025-12-02 09:11:56 +02:00

#954 Добавление типизированного значения в не типизированную коллекцию (#1084)

This commit is contained in:
Timur Mukhamedishin
2022-09-17 01:21:50 +07:00
committed by GitHub
parent c8524548de
commit 0bc1f9d9f7
13 changed files with 447 additions and 34 deletions

View File

@@ -17,6 +17,7 @@
#### Код модулей
- Добавление типизированного значения в не типизированную коллекцию
#### Запросы
@@ -82,7 +83,6 @@
- Структура модуля. Область объявления переменных.
- Из проверка doc-comment-parameter-section выделена проверка doc-comment-redundant-parameter-section
#### Запросы
- В качестве правого операнда операции сравнения "ПОДОБНО" указано поле таблицы

View File

@@ -0,0 +1,30 @@
# Добавление типизированного значения в не типизированную коллекцию
Проверяет, что вызов метода ```Добавить()``` происходит для типизированной коллекции.
## Неправильно
Тип элементов коллекции не указан.
```bsl
// @strict-types
Результат = Новый Массив();
Результат.Добавить(42);
```
## Правильно
Необходимо указать тип элементов коллекции.
```bsl
// @strict-types
Результат = Новый Массив(); // Массив из Число
Результат.Добавить(42);
```
## См.

View File

@@ -0,0 +1,26 @@
# Typed value is added to untyped collection
Checks that collection method ```Add()``` is calling for untyped collection
## Noncompliant Code Example
```bsl
// @strict-types
Result = New Array();
Result.Add(42);
```
## Compliant Solution
```bsl
// @strict-types
Result = New Array(); // Array of Number
Result.Add(42);
```
## See

View File

@@ -128,6 +128,10 @@
category="com.e1c.v8codestyle.bsl.strict"
class="com.e1c.v8codestyle.internal.bsl.ExecutableExtensionFactory:com.e1c.v8codestyle.bsl.strict.check.StructureCtorValueTypeCheck">
</check>
<check
category="com.e1c.v8codestyle.bsl.strict"
class="com.e1c.v8codestyle.internal.bsl.ExecutableExtensionFactory:com.e1c.v8codestyle.bsl.strict.check.TypedValueAddingToUntypedCollectionCheck">
</check>
<check
category="com.e1c.v8codestyle.bsl"
class="com.e1c.v8codestyle.internal.bsl.ExecutableExtensionFactory:com.e1c.v8codestyle.bsl.check.EventHandlerBooleanParamCheck">

View File

@@ -40,7 +40,9 @@ import com._1c.g5.v8.dt.bsl.documentation.comment.BslMultiLineCommentDocumentati
import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.ExplicitVariable;
import com._1c.g5.v8.dt.bsl.model.FeatureAccess;
import com._1c.g5.v8.dt.bsl.model.FeatureEntry;
import com._1c.g5.v8.dt.bsl.model.Invocation;
import com._1c.g5.v8.dt.bsl.model.Method;
import com._1c.g5.v8.dt.bsl.model.SimpleStatement;
import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.Variable;
@@ -265,6 +267,47 @@ public abstract class AbstractTypeCheck
}
}
/**
* Returns a method source for given feature access
* @param object {@link FeatureAccess}, cannot be <code>null</code>
* @return the source, can return {@code null}
*/
protected EObject getSourceMethod(FeatureAccess object)
{
Environments actualEnvs = getActualEnvironments(object);
if (actualEnvs.isEmpty())
{
return null;
}
List<FeatureEntry> objects = dynamicFeatureAccessComputer.resolveObject(object, actualEnvs);
for (FeatureEntry entry : objects)
{
EObject source = entry.getFeature();
if (source instanceof Method || (source instanceof com._1c.g5.v8.dt.mcore.Method))
{
return source;
}
}
return null;
}
/**
* Returns an actual environments for given object
* @param object {@link EObject}, cannot be <code>null</code>
* @return the environments, can return {@code Environments.EMPTY} value
*/
protected Environments getActualEnvironments(EObject object)
{
Environmental envs = EcoreUtil2.getContainerOfType(object, Environmental.class);
if (envs == null)
{
return Environments.EMPTY;
}
return bslPreferences.getLoadEnvs(object).intersect(envs.environments());
}
private static Collection<? extends String> getCastingType(Collection<String> expectedTypesNames)
{
List<String> castTypeNames = new ArrayList<>();

View File

@@ -47,7 +47,6 @@ 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;
import com._1c.g5.v8.dt.bsl.model.FeatureEntry;
import com._1c.g5.v8.dt.bsl.model.FormalParam;
import com._1c.g5.v8.dt.bsl.model.Invocation;
import com._1c.g5.v8.dt.bsl.model.Method;
@@ -59,7 +58,6 @@ import com._1c.g5.v8.dt.core.platform.IResourceLookup;
import com._1c.g5.v8.dt.core.platform.IV8Project;
import com._1c.g5.v8.dt.core.platform.IV8ProjectManager;
import com._1c.g5.v8.dt.mcore.DuallyNamedElement;
import com._1c.g5.v8.dt.mcore.Environmental;
import com._1c.g5.v8.dt.mcore.McorePackage;
import com._1c.g5.v8.dt.mcore.NamedElement;
import com._1c.g5.v8.dt.mcore.ParamSet;
@@ -497,37 +495,6 @@ public class InvocationParamIntersectionCheck
resultAceptor.addIssue(message, param, BslPackage.Literals.EXPRESSION__TYPES);
}
private EObject getSourceMethod(FeatureAccess object)
{
Environments actualEnvs = getActualEnvironments(object);
if (actualEnvs.isEmpty())
{
return null;
}
List<FeatureEntry> objects = dynamicFeatureAccessComputer.resolveObject(object, actualEnvs);
for (FeatureEntry entry : objects)
{
EObject source = entry.getFeature();
if (source instanceof Method || (source instanceof com._1c.g5.v8.dt.mcore.Method))
{
return source;
}
}
return null;
}
private Environments getActualEnvironments(EObject object)
{
Environmental envs = EcoreUtil2.getContainerOfType(object, Environmental.class);
if (envs == null)
{
return Environments.EMPTY;
}
return bslPreferences.getLoadEnvs(object).intersect(envs.environments());
}
private List<ParamSet> actualParamSet(com._1c.g5.v8.dt.mcore.Method method, int numParam)
{
List<ParamSet> result = new ArrayList<>();

View File

@@ -58,6 +58,8 @@ final class Messages
public static String StructureCtorValueTypeCheck_Structure_key__N__K__has_no_default_value_initializer;
public static String StructureCtorValueTypeCheck_Structure_key__N__K__value_initialized_with_empty_types;
public static String StructureCtorValueTypeCheck_title;
public static String TypedValueAddingToUntypedCollectionCheck_description;
public static String TypedValueAddingToUntypedCollectionCheck_title;
public static String VariableTypeCheck_description;
public static String VariableTypeCheck_title;
public static String VariableTypeCheck_Variable_M_has_no_value_type;

View File

@@ -0,0 +1,220 @@
/*******************************************************************************
* Copyright (C) 2022, 1C-Soft LLC and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* 1C-Soft LLC - initial API and implementation
*******************************************************************************/
package com.e1c.v8codestyle.bsl.strict.check;
import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.DYNAMIC_FEATURE_ACCESS;
import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.FEATURE_ACCESS__NAME;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import com._1c.g5.v8.dt.bsl.common.IBslPreferences;
import com._1c.g5.v8.dt.bsl.model.DynamicFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.FeatureAccess;
import com._1c.g5.v8.dt.bsl.model.Invocation;
import com._1c.g5.v8.dt.bsl.model.SourceObjectLinkProvider;
import com._1c.g5.v8.dt.bsl.model.util.BslUtil;
import com._1c.g5.v8.dt.core.platform.IResourceLookup;
import com._1c.g5.v8.dt.mcore.Method;
import com._1c.g5.v8.dt.mcore.Type;
import com._1c.g5.v8.dt.mcore.TypeItem;
import com._1c.g5.v8.dt.mcore.util.McoreUtil;
import com._1c.g5.v8.dt.platform.IEObjectTypeNames;
import com.e1c.g5.v8.dt.check.CheckComplexity;
import com.e1c.g5.v8.dt.check.ICheckParameters;
import com.e1c.g5.v8.dt.check.components.ModuleTopObjectNameFilterExtension;
import com.e1c.g5.v8.dt.check.settings.IssueSeverity;
import com.e1c.g5.v8.dt.check.settings.IssueType;
import com.google.inject.Inject;
/**
* Checks that typed value is added to an untyped collection
*
* @author Timur Mukhamedishin
*/
public class TypedValueAddingToUntypedCollectionCheck
extends AbstractTypeCheck
{
private static final String CHECK_ID = "typed-value-adding-to-untyped-collection"; //$NON-NLS-1$
private static final String MAP_VALUE = "Value"; //$NON-NLS-1$
//@formatter:off
private static final Map<String, Map<String, Integer>> COLLECTION_ADD_METHODS = Map.of(
"Add", Map.of(IEObjectTypeNames.ARRAY, 0, //$NON-NLS-1$
IEObjectTypeNames.VALUE_LIST, 0),
"Insert", Map.of( //$NON-NLS-1$
IEObjectTypeNames.ARRAY, 1,
IEObjectTypeNames.VALUE_LIST, 1),
"Set", Map.of( //$NON-NLS-1$
IEObjectTypeNames.ARRAY, 1)
);
/**
* Instantiates a new typed value adding to untyped collection check
*
* @param resourceLookup the resource lookup service, cannot be {@code null}.
* @param bslPreferences the BSL preferences service, cannot be {@code null}.
* @param qualifiedNameConverter the qualified name converter service, cannot be {@code null}.
*/
@Inject
public TypedValueAddingToUntypedCollectionCheck(IResourceLookup resourceLookup, IBslPreferences bslPreferences,
IQualifiedNameConverter qualifiedNameConverter)
{
super(resourceLookup, bslPreferences, qualifiedNameConverter);
}
@Override
public String getCheckId()
{
return CHECK_ID;
}
@Override
protected void configureCheck(CheckConfigurer builder)
{
builder.title(Messages.TypedValueAddingToUntypedCollectionCheck_title)
.description(Messages.TypedValueAddingToUntypedCollectionCheck_description)
.complexity(CheckComplexity.NORMAL)
.severity(IssueSeverity.MAJOR)
.issueType(IssueType.CODE_STYLE)
.extension(new ModuleTopObjectNameFilterExtension())
.extension(new StrictTypeAnnotationCheckExtension())
.module()
.checkedObjectType(DYNAMIC_FEATURE_ACCESS);
}
@Override
protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters,
IProgressMonitor monitor)
{
if (monitor.isCanceled() || !(object instanceof EObject))
{
return;
}
FeatureAccess fa = (FeatureAccess)object;
EObject method = getSourceMethod(fa);
if (!(method instanceof Method) || ((Method)method).getParamSet().isEmpty())
{
return;
}
Collection<TypeItem> expectedCollectionTypes = getExpectedCollectionTypes(fa, (Method)method);
if (expectedCollectionTypes.isEmpty())
{
return;
}
Collection<TypeItem> actualTypes = getActualCollectionTypes(fa, expectedCollectionTypes);
if (!actualTypes.isEmpty() && isActualCollectionItemTypeEmpty(actualTypes))
{
resultAceptor.addIssue(Messages.TypedValueAddingToUntypedCollectionCheck_title, FEATURE_ACCESS__NAME);
}
}
private Collection<TypeItem> getExpectedCollectionTypes(FeatureAccess fa, Method method)
{
Collection<TypeItem> expectedTypes = new ArrayList<>();
if (method instanceof SourceObjectLinkProvider)
{
return expectedTypes;
}
Invocation inv = BslUtil.getInvocation(fa);
if (!(inv.getMethodAccess() instanceof DynamicFeatureAccess))
{
return expectedTypes;
}
Map<String, Integer> typesAndParams = COLLECTION_ADD_METHODS.get(method.getName());
if (typesAndParams == null)
{
return expectedTypes;
}
TypeItem collectionType = EcoreUtil2.getContainerOfType(method, TypeItem.class);
String typeName = collectionType == null ? null : McoreUtil.getTypeName(collectionType);
if (typeName == null || !typesAndParams.containsKey(typeName))
{
return expectedTypes;
}
int parameterNumber = typesAndParams.get(typeName);
if (inv.getParams().size() < parameterNumber + 1)
{
return expectedTypes;
}
expectedTypes = typeComputer
.computeTypes(((DynamicFeatureAccess)inv.getMethodAccess()).getSource(), getActualEnvironments(inv));
return expectedTypes;
}
private Collection<TypeItem> getActualCollectionTypes(FeatureAccess fa, Collection<TypeItem> expectedTypes)
{
Collection<TypeItem> actualTypes = new ArrayList<>();
Invocation inv = BslUtil.getInvocation(fa);
for (TypeItem type : expectedTypes)
{
type = (TypeItem)EcoreUtil.resolve(type, inv);
if (type.getName().equals(IEObjectTypeNames.VALUE_LIST))
{
List<TypeItem> valueListItemTypes = ((Type)type).getCollectionElementTypes().allTypes();
Set<TypeItem> collectionTypes =
dynamicFeatureAccessComputer.getAllProperties(valueListItemTypes, null)
.stream()
.flatMap(e -> e.getFirst().stream())
.filter(p -> p.getName().equals(MAP_VALUE))
.flatMap(p -> p.getTypes().stream())
.collect(Collectors.toSet());
actualTypes.addAll(collectionTypes);
}
else
{
actualTypes.addAll(((Type)type).getCollectionElementTypes().allTypes());
}
}
return actualTypes;
}
private boolean isActualCollectionItemTypeEmpty(Collection<TypeItem> actualTypes)
{
actualTypes.removeIf(p -> McoreUtil.getTypeName(p).equals(IEObjectTypeNames.ARBITRARY));
return actualTypes.isEmpty();
}
}

View File

@@ -83,6 +83,10 @@ StructureCtorValueTypeCheck_description = Checks Structure constructor string li
StructureCtorValueTypeCheck_title = Structure constructor value types
TypedValueAddingToUntypedCollectionCheck_description = Typed value is added to untyped collection
TypedValueAddingToUntypedCollectionCheck_title = Typed value is added to untyped collection
VariableTypeCheck_Variable_M_has_no_value_type = Variable "{0}" has no value type
VariableTypeCheck_description = Check of module strict types system that every variable has value type

View File

@@ -84,6 +84,10 @@ StructureCtorValueTypeCheck_description = Проверяет строковый
StructureCtorValueTypeCheck_title = Типизация значений в конструкторе структуры
TypedValueAddingToUntypedCollectionCheck_description = Добавление типизированного значения в не типизированную коллекцию
TypedValueAddingToUntypedCollectionCheck_title = Добавление типизированного значения в не типизированную коллекцию
VariableTypeCheck_Variable_M_has_no_value_type = Переменная "{0}" не имеет типа
VariableTypeCheck_description = Система строгой типизации кода проверяет что каждая переменная имеет тип значения

View File

@@ -0,0 +1,25 @@
// @strict-types
Procedure NonComplaint() Export
result = New Array(); // Array of Number
result.Add(42);
result.Insert(0, 42);
result.Set(0, 42);
result.Add();
result.Insert(0);
EndProcedure
Procedure Complaint() Export
result = New Array();
result.Add(42);
result.Insert(0, 42);
result.Set(0, 42);
result.Add();
result.Insert(0);
EndProcedure

View File

@@ -0,0 +1,23 @@
// @strict-types
Procedure NonComplaint() Export
result = New ValueList(); // ValueList of Number
result.Add(42);
result.Insert(0, 42);
result.Add();
result.Insert(0);
EndProcedure
Procedure Complaint() Export
result = New Array();
result.Add(42);
result.Insert(0, 42);
result.Add();
result.Insert(0);
EndProcedure

View File

@@ -56,6 +56,7 @@ import com.e1c.v8codestyle.bsl.strict.check.InvocationParamIntersectionCheck;
import com.e1c.v8codestyle.bsl.strict.check.MethodParamTypeCheck;
import com.e1c.v8codestyle.bsl.strict.check.SimpleStatementTypeCheck;
import com.e1c.v8codestyle.bsl.strict.check.StructureCtorValueTypeCheck;
import com.e1c.v8codestyle.bsl.strict.check.TypedValueAddingToUntypedCollectionCheck;
import com.e1c.v8codestyle.bsl.strict.check.VariableTypeCheck;
/**
@@ -575,6 +576,70 @@ public class CommonModuleStrictTypesTest
assertTrue(markers.isEmpty());
}
/**
* Test of {@link TypedValueAddingToUntypedCollectionCheck} that typed value is adding
* to untyped array.
*
* @throws Exception the exception
*/
@Test
public void testTypedValueAddingToUntypedCollectionCheck() throws Exception
{
String checkId = "typed-value-adding-to-untyped-collection";
String resouceName = "typed-value-adding-to-untyped-array";
Module module = updateAndGetModule(resouceName);
List<DynamicFeatureAccess> statements = EcoreUtil2.eAllOfType(module, DynamicFeatureAccess.class);
assertEquals(10, statements.size());
List<Marker> markers = getMarters(checkId, module);
assertEquals(3, markers.size());
Set<String> lines = new HashSet<>();
for (Marker m : markers)
{
lines.add(m.getExtraInfo().get(IExtraInfoKeys.TEXT_EXTRA_INFO_LINE_KEY));
}
assertEquals(Set.of("19", "20", "21"), lines);
}
/**
* Test of {@link TypedValueAddingToUntypedCollectionCheck} that typed value is adding
* to untyped value list.
*
* @throws Exception the exception
*/
@Test
public void testTypedValueAddingToUntypedValueListCheck() throws Exception
{
String checkId = "typed-value-adding-to-untyped-collection";
String resouceName = "typed-value-adding-to-untyped-value-list";
Module module = updateAndGetModule(resouceName);
List<DynamicFeatureAccess> statements = EcoreUtil2.eAllOfType(module, DynamicFeatureAccess.class);
assertEquals(8, statements.size());
List<Marker> markers = getMarters(checkId, module);
assertEquals(2, markers.size());
Set<String> lines = new HashSet<>();
for (Marker m : markers)
{
lines.add(m.getExtraInfo().get(IExtraInfoKeys.TEXT_EXTRA_INFO_LINE_KEY));
}
assertEquals(Set.of("18", "19"), lines);
}
private IDtProject getProject()
{
return dtProject;