diff --git a/bundles/com.e1c.v8codestyle.bsl/META-INF/MANIFEST.MF b/bundles/com.e1c.v8codestyle.bsl/META-INF/MANIFEST.MF index ee3388a5..14c9914e 100644 --- a/bundles/com.e1c.v8codestyle.bsl/META-INF/MANIFEST.MF +++ b/bundles/com.e1c.v8codestyle.bsl/META-INF/MANIFEST.MF @@ -29,6 +29,7 @@ Import-Package: com._1c.g5.v8.bm.core;version="[9.0.0,10.0.0)", com._1c.g5.v8.dt.bsl.model.util;version="[4.0.0,5.0.0)", com._1c.g5.v8.dt.bsl.resource;version="[15.0.0,16.0.0)", com._1c.g5.v8.dt.bsl.services;version="[7.0.0,8.0.0)", + com._1c.g5.v8.dt.bsl.stringliteral.contenttypes;version="[1.2.0,2.0.0)", com._1c.g5.v8.dt.bsl.typesystem;version="[10.0.0,11.0.0)", com._1c.g5.v8.dt.bsl.typesystem.util;version="[11.0.0,12.0.0)", com._1c.g5.v8.dt.bsl.util;version="[8.0.0,9.0.0)", diff --git a/bundles/com.e1c.v8codestyle.bsl/plugin.xml b/bundles/com.e1c.v8codestyle.bsl/plugin.xml index 84f46c15..20d13a22 100644 --- a/bundles/com.e1c.v8codestyle.bsl/plugin.xml +++ b/bundles/com.e1c.v8codestyle.bsl/plugin.xml @@ -391,6 +391,10 @@ category="com.e1c.v8codestyle.bsl" class="com.e1c.v8codestyle.bsl.check.OptionalFormParameterAccessCheck"> + + diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java index f5d3b1d2..73d961c5 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/Messages.java @@ -453,7 +453,7 @@ final class Messages public static String IsInRoleMethodRoleExistCheck_Role_named_not_exists_in_configuration; public static String IsInRoleMethodRoleExistCheck_title; - + public static String ModuleUndefinedVariableCheck_Title; public static String ModuleUndefinedVariableCheck_Description; public static String ModuleUndefinedVariable_msg; @@ -484,6 +484,9 @@ final class Messages public static String VariableNameInvalidCheck_variable_name_must_start_with_a_capital_letter; public static String VariableNameInvalidCheck_variable_name_starts_with_an_underline; + public static String StringLiteralTypeAnnotationCheck_Title; + public static String StringLiteralTypeAnnotationCheck_Incorrect_annotation_location; + static { // initialize resource bundle diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/StringLiteralTypeAnnotationCheck.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/StringLiteralTypeAnnotationCheck.java new file mode 100644 index 00000000..451fe904 --- /dev/null +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/StringLiteralTypeAnnotationCheck.java @@ -0,0 +1,242 @@ +/** + * Copyright (C) 2025, 1C + */ +package com.e1c.v8codestyle.bsl.check; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.ILeafNode; +import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.util.Triple; + +import com._1c.g5.v8.dt.bsl.documentation.comment.BslCommentUtils; +import com._1c.g5.v8.dt.bsl.model.Conditional; +import com._1c.g5.v8.dt.bsl.model.IfStatement; +import com._1c.g5.v8.dt.bsl.model.LoopStatement; +import com._1c.g5.v8.dt.bsl.model.Module; +import com._1c.g5.v8.dt.bsl.model.PreprocessorItem; +import com._1c.g5.v8.dt.bsl.model.RegionPreprocessor; +import com._1c.g5.v8.dt.bsl.model.StringLiteral; +import com._1c.g5.v8.dt.bsl.model.TryExceptStatement; +import com._1c.g5.v8.dt.bsl.stringliteral.contenttypes.BslBuiltInLanguagePreferences; +import com._1c.g5.v8.dt.bsl.stringliteral.contenttypes.IStringLiteralTypeComputer; +import com._1c.g5.v8.dt.bsl.stringliteral.contenttypes.LiteralType; +import com._1c.g5.v8.dt.bsl.stringliteral.contenttypes.TypeUtil; +import com._1c.g5.v8.dt.common.StringUtils; +import com._1c.g5.v8.dt.core.platform.IV8Project; +import com._1c.g5.v8.dt.core.platform.IV8ProjectManager; +import com.e1c.g5.v8.dt.check.BslDirectLocationIssue; +import com.e1c.g5.v8.dt.check.CheckComplexity; +import com.e1c.g5.v8.dt.check.DirectLocation; +import com.e1c.g5.v8.dt.check.ICheckParameters; +import com.e1c.g5.v8.dt.check.components.BasicCheck; +import com.e1c.g5.v8.dt.check.settings.IssueSeverity; +import com.e1c.g5.v8.dt.check.settings.IssueType; +import com.e1c.v8codestyle.check.CommonSenseCheckExtension; +import com.e1c.v8codestyle.internal.bsl.BslPlugin; +import com.google.inject.Inject; + +/** + * Checks the correct placement of annotations for typing string literals. + * + * @author Babin Nikolay + * + */ +public class StringLiteralTypeAnnotationCheck + extends BasicCheck +{ + private static final String CHECK_ID = "string-literal-type-annotation-invalid-place"; //$NON-NLS-1$ + + @Inject + private IV8ProjectManager projectManager; + + @Inject + private IStringLiteralTypeComputer typeComputer; + + private final AtomicReference> annotations = new AtomicReference<>(); + + @Override + public String getCheckId() + { + return CHECK_ID; + } + + @Override + protected void configureCheck(CheckConfigurer builder) + { + builder.title(Messages.StringLiteralTypeAnnotationCheck_Title) + .complexity(CheckComplexity.NORMAL) + .severity(IssueSeverity.MAJOR) + .issueType(IssueType.WARNING) + .extension(new CommonSenseCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID)) + .module(); + } + + @Override + protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters, + IProgressMonitor monitor) + { + if (!(object instanceof Module)) + return; + + Module module = (Module)object; + + if (!isApplyTagsToEntireExpression(module)) + return; + + ICompositeNode moduleNode = NodeModelUtils.findActualNodeFor(module); + List moduleStringLiterals = new ArrayList<>(); + List moduleAnnotations = new ArrayList<>(); + if (moduleNode != null) + { + for (ILeafNode child : moduleNode.getLeafNodes()) + { + if (monitor.isCanceled()) + return; + + EObject semantic = NodeModelUtils.findActualSemanticObjectFor(child); + if (semantic instanceof StringLiteral literal) + { + moduleStringLiterals.add(literal); + } + if (child.isHidden() && BslCommentUtils.isCommentNode(child) && isAllowAnnotation(child.getText())) + { + moduleAnnotations.add(child); + } + } + } + + List invalidAnnotations = getInvalidAnnotations(monitor, moduleStringLiterals, moduleAnnotations); + + addIssues(resultAceptor, module, invalidAnnotations, monitor); + } + + private void addIssues(ResultAcceptor resultAceptor, Module module, List invalidAnnotations, + IProgressMonitor monitor) + { + for (INode annotation : invalidAnnotations) + { + if (monitor.isCanceled()) + return; + + int index = annotation.getText().indexOf("@"); //$NON-NLS-1$ + int offset = annotation.getTotalOffset() + index; + + int length = annotation.getText() + .trim() + .replaceFirst(BslCommentUtils.START_COMMENT_TAG_BSL, StringUtils.EMPTY) + .trim() + .toLowerCase() + .length(); + + DirectLocation directLocation = new DirectLocation(offset, length, annotation.getStartLine(), module); + BslDirectLocationIssue directLocationIssue = + new BslDirectLocationIssue( + Messages.StringLiteralTypeAnnotationCheck_Incorrect_annotation_location, directLocation, + StringUtils.EMPTY); + + resultAceptor.addIssue(directLocationIssue); + } + } + + private List getInvalidAnnotations(IProgressMonitor monitor, List moduleStringLiterals, + List moduleAnnotations) + { + List invalidAnnotations = new ArrayList<>(); + + Set correctAnnotations = new HashSet<>(); + for (StringLiteral literal : moduleStringLiterals) + { + if (monitor.isCanceled()) + return invalidAnnotations; + + EObject literalParent = findLiteralParent(literal); + + if (literalParent != null) + { + List rightLines = TypeUtil.getCommentLinesFromRight(literalParent) + .stream() + .filter(node -> isAllowAnnotation(node.getText())) + .toList(); + correctAnnotations.addAll(rightLines); + } + } + + for (INode node : moduleAnnotations) + { + if (monitor.isCanceled()) + return invalidAnnotations; + + if (!correctAnnotations.contains(node)) + { + invalidAnnotations.add(node); + } + } + return invalidAnnotations; + } + + private boolean isApplyTagsToEntireExpression(EObject object) + { + IV8Project project = projectManager.getProject(object); + + return project != null && project.getProject() != null + && BslBuiltInLanguagePreferences.isApplyTagsToEntireExpression(project.getProject()); + } + + private EObject findLiteralParent(StringLiteral literal) + { + for (EObject e = literal; e != null; e = e.eContainer()) + { + EObject container = e.eContainer(); + //@formatter:off + if (container instanceof com._1c.g5.v8.dt.bsl.model.Method + || container instanceof RegionPreprocessor + || container instanceof PreprocessorItem + || container instanceof Conditional + || container instanceof IfStatement + || container instanceof TryExceptStatement + || container instanceof LoopStatement) + { + //@formatter:on + return e; + } + } + return null; + } + + private boolean isAllowAnnotation(String text) + { + List> commentAnnotations = TypeUtil.parseHeaderAnnotations(text); + for (Triple commentAnnotation : commentAnnotations) + { + if (getAllowAnnotations().contains(commentAnnotation.getFirst().toLowerCase())) + return true; + } + return false; + } + + private Set getAllowAnnotations() + { + Set allowAnnotations = annotations.get(); + if (allowAnnotations == null) + { + allowAnnotations = typeComputer.allTypes() + .stream() + .filter(LiteralType::allowAnnotation) + .map(type -> type.getName().toLowerCase()) + .collect(Collectors.toSet()); + if (!annotations.compareAndSet(null, allowAnnotations)) + allowAnnotations = annotations.get(); + } + return allowAnnotations; + } +} diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties index 2b2d397b..4f742843 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages.properties @@ -513,3 +513,7 @@ VariableNameInvalidCheck_variable_name_is_invalid = Variable name {0} is invalid VariableNameInvalidCheck_variable_name_must_start_with_a_capital_letter = variable name must start with a capital letter VariableNameInvalidCheck_variable_name_starts_with_an_underline = variable name starts with an underline + +StringLiteralTypeAnnotationCheck_Title=The annotation is placed in the wrong location + +StringLiteralTypeAnnotationCheck_Incorrect_annotation_location=Incorrect location for placing the annotation diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties index 9eadbaf3..7710b449 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/bsl/check/messages_ru.properties @@ -513,3 +513,7 @@ VariableNameInvalidCheck_variable_name_is_invalid = Имя переменной VariableNameInvalidCheck_variable_name_must_start_with_a_capital_letter = имя переменной должно начинаться с заглавной буквы VariableNameInvalidCheck_variable_name_starts_with_an_underline = имя переменной начинается с символа подчеркивания + +StringLiteralTypeAnnotationCheck_Title = Неправильное размещение аннотации строковых литералов + +StringLiteralTypeAnnotationCheck_Incorrect_annotation_location=Неправильное размещение аннотации типов строковых литералов diff --git a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/internal/bsl/ExternalDependenciesModule.java b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/internal/bsl/ExternalDependenciesModule.java index 6db7a920..30ce2ff1 100644 --- a/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/internal/bsl/ExternalDependenciesModule.java +++ b/bundles/com.e1c.v8codestyle.bsl/src/com/e1c/v8codestyle/internal/bsl/ExternalDependenciesModule.java @@ -36,6 +36,7 @@ import com._1c.g5.v8.dt.bsl.resource.ExportMethodProvider; import com._1c.g5.v8.dt.bsl.resource.TypesComputer; import com._1c.g5.v8.dt.bsl.services.BslGrammarAccess; +import com._1c.g5.v8.dt.bsl.stringliteral.contenttypes.IStringLiteralTypeComputer; import com._1c.g5.v8.dt.bsl.typesystem.ExportMethodTypeProvider; import com._1c.g5.v8.dt.core.naming.ITopObjectFqnGenerator; import com._1c.g5.v8.dt.core.platform.IBmModelManager; @@ -82,6 +83,7 @@ protected void doConfigure() URI uri = URI.createURI("*.bsl"); //$NON-NLS-1$ final IResourceServiceProvider rsp = IResourceServiceProvider.Registry.INSTANCE.getResourceServiceProvider(uri); + bind(IStringLiteralTypeComputer.class).toProvider(() -> rsp.get(IStringLiteralTypeComputer.class)); bind(IResourceDescription.Manager.class).toProvider(() -> rsp.get(IResourceDescription.Manager.class)); bind(BslEventsService.class).toProvider(() -> rsp.get(BslEventsService.class)); bind(TypesComputer.class).toProvider(() -> rsp.get(TypesComputer.class)); diff --git a/tests/com.e1c.v8codestyle.bsl.itests/resources/string-literal-annotations-invalid-locations.bsl b/tests/com.e1c.v8codestyle.bsl.itests/resources/string-literal-annotations-invalid-locations.bsl new file mode 100644 index 00000000..5f8521a7 --- /dev/null +++ b/tests/com.e1c.v8codestyle.bsl.itests/resources/string-literal-annotations-invalid-locations.bsl @@ -0,0 +1,36 @@ + +// Новая процедура. +// +// Параметры: +// ЯвляетсяЗаявкойНаОплату Является заявкой на оплату +// ИмяСвойства Имя свойства +// ЗначенияСвойства Значения свойства +// ОбъектXDTO Объект XDTO +Процедура НоваяПроцедура(ЯвляетсяЗаявкойНаОплату, ИмяСвойства, ЗначенияСвойства, ОбъектXDTO) + + Значение = ""; // Дата1, Число какое то число + // yj dsq + + Сообщить(Значение); + + Если ЯвляетсяЗаявкойНаОплату И ИмяСвойства = "recipient" //@non-nls + Тогда // @fqn + + ЗначенияСвойства.Добавить(ОбъектXDTO, "Получатель"); // @nOn-nls + ИначеЕсли ТипЗнч(ОбъектXDTO[ИмяСвойства]) = Тип("СписокXDTO") + + Тогда // @noN-nls-1 + Для //@form + Каждого ЭлементСпискаXDTO + //@non-nls + Из ОбъектXDTO[ИмяСвойства] Цикл + + ЗначенияСвойства.Добавить("ыва"); //@non-Nls + ЗначенияСвойства.Добавить("Литерал"); //@non-nLs + + КонецЦикла; + Иначе + ЗначенияСвойства.Добавить(ОбъектXDTO[ИмяСвойства]); + КонецЕсли; + +КонецПроцедуры diff --git a/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/check/itests/StringLiteralTypeAnnotationCheckTest.java b/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/check/itests/StringLiteralTypeAnnotationCheckTest.java new file mode 100644 index 00000000..ce3c6c84 --- /dev/null +++ b/tests/com.e1c.v8codestyle.bsl.itests/src/com/e1c/v8codestyle/bsl/check/itests/StringLiteralTypeAnnotationCheckTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2025, 1C + */ +package com.e1c.v8codestyle.bsl.check.itests; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.junit.Test; + +import com._1c.g5.v8.dt.bsl.stringliteral.contenttypes.BslBuiltInLanguagePreferences; +import com._1c.g5.v8.dt.core.platform.IDtProject; +import com._1c.g5.v8.dt.validation.marker.Marker; +import com._1c.g5.v8.dt.validation.marker.StandardExtraInfo; +import com.e1c.v8codestyle.bsl.check.StringLiteralTypeAnnotationCheck; + +/** + * A class for testing {@link StringLiteralTypeAnnotationCheck} + * + * @author Babin Nikolay + * + */ +public class StringLiteralTypeAnnotationCheckTest + extends AbstractSingleModuleTestBase +{ + + private static final String PROJECT_NAME = "CommonModule"; + + private static final String MODULE_FILE_NAME = "/src/CommonModules/CommonModule/Module.bsl"; + + public StringLiteralTypeAnnotationCheckTest() + { + super(StringLiteralTypeAnnotationCheck.class); + } + + /** + * Checks invalid annotations locations. + * + * @throws Exception the exception + */ + @Test + public void testInvalidAnnotationsLocationsMarkers() throws Exception + { + IDtProject project = getProject(); + + IEclipsePreferences preferences = BslBuiltInLanguagePreferences.getPreferences(project.getWorkspaceProject()); + preferences.putBoolean(BslBuiltInLanguagePreferences.APPLY_TAGS_TO_ENTIRE_EXPRESSION, true); + preferences.flush(); + + updateModule(FOLDER_RESOURCE + "string-literal-annotations-invalid-locations.bsl"); + + List markers = getModuleMarkers(); + assertEquals(2, markers.size()); + + assertEquals(Integer.valueOf(22), markers.get(0).getExtraInfo().get(StandardExtraInfo.TEXT_LINE)); + assertEquals(Integer.valueOf(25), markers.get(1).getExtraInfo().get(StandardExtraInfo.TEXT_LINE)); + } + + @Override + protected String getTestConfigurationName() + { + return PROJECT_NAME; + } + + @Override + protected String getModuleFileName() + { + return MODULE_FILE_NAME; + } +}