Skip to content

#745 Проверка не локализированной строки #1062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
*
*/
package com.e1c.v8codestyle.bsl.check;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.Tuples;

import com._1c.g5.v8.dt.lcore.util.CaseInsensitiveString;
import com._1c.g5.v8.dt.platform.IEObjectTypeNames;
import com.google.inject.Singleton;

/**
* @author Dmitriy Marmyshev
*
*/
@Singleton
public class LocalizableRegistry
{

private Map<CaseInsensitiveString, Collection<Integer>> staticInvocation;

private Map<Pair<CaseInsensitiveString, Integer>, Collection<String>> dynamicInvocation;

private Map<CaseInsensitiveString, Collection<String>> dynamicProperties;

private volatile boolean initialized;

public Collection<Integer> getStaticInvocationParameters(String methodName)
{
if (methodName == null)
{
return Set.of();
}
checkInit();

return staticInvocation.getOrDefault(new CaseInsensitiveString(methodName), Set.of());
}

public Collection<String> getDynamicTypesForMethod(String methodName, int index)
{
if (methodName == null || index < 0)
{
return Set.of();
}

checkInit();

return dynamicInvocation.getOrDefault(Tuples.create(new CaseInsensitiveString(methodName), index), Set.of());
}

public Collection<String> getDynamicTypesForProperty(String propertyName)
{
if (propertyName == null)
{
return Set.of();
}

checkInit();

return dynamicProperties.getOrDefault(new CaseInsensitiveString(propertyName), Set.of());
}

private void checkInit()
{
if (initialized)
{
return;
}

init();
}

private synchronized void init()
{
if (initialized)
{
return;
}

initStaticInvocation();
initDynamicInvocation();
initDynamicProperties();

initialized = true;
}

private void initStaticInvocation()
{
// Global context method name and index of localizable string parameter
Map<CaseInsensitiveString, Collection<Integer>> invocations = new HashMap<>();
invocations.put(new CaseInsensitiveString("ПоказатьВопрос"), Set.of(1, 5)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("ShowQueryBox"), Set.of(1, 5)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("Вопрос"), Set.of(0, 4)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("DoQueryBox"), Set.of(0, 4)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("Сообщить"), Set.of(0)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("Message"), Set.of(0)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("Состояние"), Set.of(0, 2)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("Status"), Set.of(0, 2)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("ПоказатьОповещениеПользователя"), Set.of(0, 2)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("ShowUserNotification"), Set.of(0, 2)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("ПоказатьПредупреждение"), Set.of(1, 3)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("ShowMessageBox"), Set.of(1, 3)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("Предупреждение"), Set.of(0, 2)); //$NON-NLS-1$
invocations.put(new CaseInsensitiveString("DoMessageBox"), Set.of(0, 2)); //$NON-NLS-1$

staticInvocation = Map.copyOf(invocations);
}

private void initDynamicInvocation()
{
//@formatter:off

// Object's method name and index of localizable string parameter, and collection of object's type names
dynamicInvocation = Map.of(
Tuples.create(new CaseInsensitiveString("Добавить"), 1), Set.of(IEObjectTypeNames.VALUE_LIST), //$NON-NLS-1$
Tuples.create(new CaseInsensitiveString("Add"), 1), Set.of(IEObjectTypeNames.VALUE_LIST) //$NON-NLS-1$
);

//@formatter:on

}

private void initDynamicProperties()
{
//@formatter:off

// Types that contains property Title
Set<String> titleTypes = Set.of(
IEObjectTypeNames.FORM_FIELD,
IEObjectTypeNames.FORM_GROUP,
IEObjectTypeNames.FORM_TABLE,
IEObjectTypeNames.FORM_DECORATION,
IEObjectTypeNames.FORM_COMMAND,
"FormAttribute", //$NON-NLS-1$
"FormItemAddition", //$NON-NLS-1$
IEObjectTypeNames.FORM_BUTTON,
IEObjectTypeNames.CLIENT_APPLICATION_FORM,
"ConditionalAppearenceItem", //$NON-NLS-1$
"AppearenceSettingItem", //$NON-NLS-1$
"CollaborationSystemConversation", //$NON-NLS-1$
"DeliverableNotification", //$NON-NLS-1$
"RepresentableDocumentBatch", //$NON-NLS-1$
"HTMLDocument", //$NON-NLS-1$
"ValueTableColumn", //$NON-NLS-1$
"ValueTreeColumn", //$NON-NLS-1$
"DataCompositionAreaTemplateValueCollectionHeaderCell", //$NON-NLS-1$
IEObjectTypeNames.DATA_COMPOSITION_USER_FIELD_EXPRESSION,
IEObjectTypeNames.DATA_COMPOSITION_USER_FIELD_CASE,
IEObjectTypeNames.DATA_COMPOSITION_SELECTED_FIELD_GROUP,
IEObjectTypeNames.DATA_COMPOSITION_SELECTED_FIELD,
IEObjectTypeNames.DATA_COMPOSITION_FILTER_AVAILABLE_FIELD,
"NestedDataCompositionSchema", //$NON-NLS-1$
"DataCompositionSchemaParameter", //$NON-NLS-1$
"DataCompositionSchemaNestedDataSet", //$NON-NLS-1$
"DataCompositionSchemaDataSetFieldFolder", //$NON-NLS-1$
"DataCompositionSchemaDataSetField", //$NON-NLS-1$
"DataCompositionSchemaCalculatedField", //$NON-NLS-1$
IEObjectTypeNames.DATA_ANALYSIS_PARAMETERS,
"GanttChartPlotArea", //$NON-NLS-1$
"FileDialog" //$NON-NLS-1$
);

// Types that contains property ToolTip
// TODO add all types with tooltip
Set<String> toolTipTypes = Set.of(
IEObjectTypeNames.FORM_FIELD,
IEObjectTypeNames.FORM_GROUP,
IEObjectTypeNames.FORM_TABLE,
IEObjectTypeNames.FORM_DECORATION,
IEObjectTypeNames.FORM_COMMAND,
"FormItemAddition", //$NON-NLS-1$
"DateAppearence" //$NON-NLS-1$
);

// TODO add types of graphical scheme with Description

// TODO add types of DCS with Presentation

// Localizable property name, and collection of types
dynamicProperties = Map.of(
new CaseInsensitiveString("Подсказка"), toolTipTypes, //$NON-NLS-1$
new CaseInsensitiveString("ToolTip"), toolTipTypes, //$NON-NLS-1$
new CaseInsensitiveString("ПодсказкаВвода"), Set.of("FormFieldExtenstionForTextBox"), //$NON-NLS-1$
new CaseInsensitiveString("InputHint"), Set.of("FormFieldExtenstionForTextBox"), //$NON-NLS-1$
new CaseInsensitiveString("Заголовок"), titleTypes, //$NON-NLS-1$
new CaseInsensitiveString("Title"), titleTypes //$NON-NLS-1$
);
//@formatter:on

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*******************************************************************************
* 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.check;

import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.STRING_LITERAL;
import static com._1c.g5.v8.dt.bsl.model.BslPackage.Literals.STRING_LITERAL__LINES;

import java.util.Collection;
import java.util.List;
import java.util.Objects;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.EcoreUtil2;

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.SimpleStatement;
import com._1c.g5.v8.dt.bsl.model.StaticFeatureAccess;
import com._1c.g5.v8.dt.bsl.model.StringLiteral;
import com._1c.g5.v8.dt.bsl.resource.TypesComputer;
import com._1c.g5.v8.dt.common.StringUtils;
import com._1c.g5.v8.dt.mcore.Environmental;
import com._1c.g5.v8.dt.mcore.TypeItem;
import com._1c.g5.v8.dt.mcore.util.McoreUtil;
import com.e1c.g5.v8.dt.check.CheckComplexity;
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;

/**
* @author Dmitriy Marmyshev
*
*/
public class NstrLocalizableStringCheck
extends BasicCheck
{
private static final String CHECK_ID = "bsl-localizable-string"; //$NON-NLS-1$

private final TypesComputer typesComputer;

private final LocalizableRegistry localizableRegistry;

@Inject
public NstrLocalizableStringCheck(TypesComputer typesComputer, LocalizableRegistry localizableRegistry)
{
this.typesComputer = typesComputer;
this.localizableRegistry = localizableRegistry;
}

@Override
public String getCheckId()
{
return CHECK_ID;
}

@Override
protected void configureCheck(CheckConfigurer builder)
{
builder.title("String literal should be localizable with NStr")
.description("String literal should be localizable with NStr")
.complexity(CheckComplexity.NORMAL)
.severity(IssueSeverity.MINOR)
.issueType(IssueType.UI_STYLE)
.extension(new CommonSenseCheckExtension(getCheckId(), BslPlugin.PLUGIN_ID))
.module()
.checkedObjectType(STRING_LITERAL);

}

@Override
protected void check(Object object, ResultAcceptor resultAceptor, ICheckParameters parameters,
IProgressMonitor monitor)
{
StringLiteral literal = (StringLiteral)object;

if (literal.getLines().size() == 1 && StringUtils.isBlank(literal.lines(true).get(0)))
{
// skip empty lines
return;
}

EObject parent = literal.eContainer();

if (parent instanceof Invocation && isLocalizableParameter((Invocation)parent, literal, monitor)
|| parent instanceof SimpleStatement && ((SimpleStatement)parent).getLeft() instanceof DynamicFeatureAccess
&& !monitor.isCanceled()
&& isLocalizableProperty((DynamicFeatureAccess)((SimpleStatement)parent).getLeft(), monitor))
{
resultAceptor.addIssue("Localizable string should be in NStr()", STRING_LITERAL__LINES);
}

}

private boolean isLocalizableParameter(Invocation inv, EObject parameter, IProgressMonitor monitor)
{
FeatureAccess method = inv.getMethodAccess();
if (StringUtils.isBlank(method.getName()))
{
return false;
}

if (method instanceof StaticFeatureAccess && !monitor.isCanceled())
{
Collection<Integer> params = localizableRegistry.getStaticInvocationParameters(method.getName());
if (!params.isEmpty())
{
int index = inv.getParams().indexOf(parameter);
return params.contains(index);
}
}
else if (method instanceof DynamicFeatureAccess && !monitor.isCanceled())
{
int index = inv.getParams().indexOf(parameter);
DynamicFeatureAccess dfa = (DynamicFeatureAccess)method;
Collection<String> typeNames = localizableRegistry.getDynamicTypesForMethod(dfa.getName(), index);
if (!typeNames.isEmpty() && !monitor.isCanceled())
{
Environmental env = EcoreUtil2.getContainerOfType(dfa, Environmental.class);
List<TypeItem> types = typesComputer.computeTypes(dfa.getSource(), env.environments());
return !types.isEmpty() && types.stream()
.map(McoreUtil::getTypeName)
.filter(Objects::nonNull)
.anyMatch(typeNames::contains);
}
}
return false;
}

private boolean isLocalizableProperty(DynamicFeatureAccess property, IProgressMonitor monitor)
{
String name = property.getName();
if (StringUtils.isBlank(name))
{
return false;
}
Collection<String> typeNames = localizableRegistry.getDynamicTypesForProperty(name);

if (!typeNames.isEmpty() && !monitor.isCanceled())
{
Environmental env = EcoreUtil2.getContainerOfType(property, Environmental.class);
List<TypeItem> types = typesComputer.computeTypes(property.getSource(), env.environments());
return !types.isEmpty()
&& types.stream().map(McoreUtil::getTypeName).filter(Objects::nonNull).anyMatch(typeNames::contains);
}

return false;
}

}