Работа с модулями встроенного языка

Работа с модулями встроенного языка #

Сервисы для работы со встроенным языком #

org.eclipse.xtext.nodemodel.util.NodeModelUtils #

Данный сервис предназначен для работы с лексическим деревом разбора кода программных модулей. При помощи данного сервиса можно по семантическим элементам модели модуля получить их расположение внутри программного текста, получить фрагменты кода, по которым был создан данный семантический элемент.

org.eclipse.xtext.EcoreUtil2 #

Данный сервис содержит набор методов, которые позволяют более удобным образом производить навигацию по семантической модели языка.

com._1c.g5.v8.dt.bsl.commentBslMultiLineCommentDocumentationProvider #

Данный провайдер позволяет получать комментарии, относящиеся к семантическим элементам модели встроенного языка.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class FooExample
{
    @Inject
    private BslMultiLineCommentDocumentationProvider commentProvider;
    
    public String parseMethodComment(Method method)
    {
        String commentContent = commentProvider.getDocumentation(method);
        return parseContent(commentContent);
    }
 
	private String parseContent(String commentContent)
	{
		// Парсим содержание комментария
		// ...
	}
}

org.eclipse.xtext.naming.IQualifiedNameProvider #

Сервис com._1c.g5.v8.dt.bsl.naming.BslQualifiedNameProvider, реализующий данный интерфейс, предназначен для получения org.eclipse.xtext.naming.QualifiedName по семантическим элементам модели встроенного языка «1С: Предприятия».

org.eclipse.xtext.resource.EObjectAtOffsetHelper #

Данный сервис предназначен для получения семантических элементов модели встроенного языка по позиции внутри модуля.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class FooExample
{
    @Inject
    private EObjectAtOffsetHelper offsetHelper;

    public Method getMethodByOffset(XtextResource resource, int offset)
    {
        EObject semanticObject = offsetHelper.resolveElementAt(resource, offset);
        return EcoreUtil2.getContainerOfType(semanticObject, Method.class);
    }
}

org.eclipse.xtext.resource.ILocationInFileProvider #

Сервис com._1c.g5.v8.dt.bsl.resource.BslLocationInFileProvider, реализующий данный интерфейс, предназначен для получения позиции семантического элемента модели встроенного языка в программном модуле.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class FooExample
{
    @Inject
    private ILocationInFileProvider locationProvider;

    public ITextRegion getMethodPosition(EObject semanticObject)
    {
        Method method = EcoreUtil2.getContainerOfType(semanticObject, Method.class);
        if (method != null)
        {
            return locationProvider.getFullTextRegion(method);
        }
        else
        {
            return ITextRegion.EMPTY_REGION;
        }
    }
}

com._1c.g5.v8.dt.bsl.resource.TypesComputer #

Данный сервис позволяет вычислять тип всех семантических элементов модели, которые наследуются от com._1c.g5.v8.dt.bsl.model.Expression. Типы возвращаемые данным сервисом не следует модифицировать (изменять внутренний контент, изменять контейнера данного объекта). Если же такая потребность возникла, то сначала нужно склонировать возвращаемый тип, воспользовавшись методом org.eclipse.xtext.EcoreUtil2.cloneWithProxies().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class FooExample
{
    @Inject
    private TypesComputer typesComputer;
 
    public boolean hasNumberType(Expression expression, Environments defaultEnvironments)
    {
        Environmental environmental = EcoreUtil2.getContainerOfType(expression, Environmental.class);
        Environments environments = environmental != null ? environmental.environments() : defaultEnvironments;
        Collection<TypeItem> types = typesComputer.compute(expression, environments);
        for (TypeItem type : types)
        {
            if (IEObjectTypeNames.NUMBER.equals(McoreUtil.getTypeName(type)))
                return true;
        }
        return false;
    }
}

org.eclipse.xtext.scoping.IScopeProvider #

Сервис com._1c.g5.v8.dt.bsl.scoping.BslScopeProvider, реализующий данный интерфейс, предназначен для получения наборов индексируемых объектов, таких как:

  • Переменные и методы модуля,
  • Свойства и методы глобального контекста,
  • Платформенные типы,
  • Типы объектов конфигурации,
  • Объекты конфигурации.

Набор объектов можно получить лишь в том случае, если передать правильный контекст (первый аргумент в методе org.eclipse.xtext.scoping.IScopeProvider.getScope(...)). В качестве него нужно использовать элементы семантической модели встроенного языка.

Получение ссылочных типов справочников

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class FooExample
{
    @Inject
    private IScopeProvider scopeProvider;
 
    public Collection<TypeItem> getCatalogRefTypes(EObject context)
    {
        IScope scope = scopeProvider.getScope(context, BslPackage.Literals.OPERATOR_STYLE_CREATOR__TYPE);
        Collection<TypeItem> allCatalogTypes = Lists.newArrayList();
        for (IEObjectDescription item : scope.getAllElements())
        {
            if ("CatalogRef".equals(item.getName().getFirstSegment()))
                allCatalogTypes.add((TypeItem)item.getEObjectOrProxy());
        }
        return allCatalogTypes;
    }
}

Получение всех методов, которые могут быть использованы в контексте метода

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class FooExample
{
    @Inject
    private IScopeProvider scopeProvider;
    public Collection<String> getAllAllowedMethodNames(Module module, Method method)
    {
        MethodsScopeSpec spec = BslFactory.eINSTANCE.createMethodsScopeSpec();
        spec.setModule(module);
        spec.setMethod(method);
        spec.setIgnoreModuleItems(false);
        spec.setIgnoreServerCalls(false);
        Set<String> allMethodNames = Sets.newHashSet();
        Environments actualEnvironments = (method != null) ? method.environments() : module.environments();
        for (Environment environment : actualEnvironments.toArray())
        {
            spec.setEnvironment(environment);
            IScope scope = scopeProvider.getScope(spec, BslPackage.Literals.METHODS_SCOPE_SPEC__METHOD_REF);
            for (IEObjectDescription item : scope.getAllElements())
            {
                if (BslPackage.Literals.METHOD.equals(item.getEClass())
                    || McorePackage.Literals.METHOD.equals(item.getEClass()))
                {
                    allMethodNames.add(item.getName().toString());
                }
            }
        }
        return allMethodNames;
    }
}

Описание точек расширения встроенного языка #

Расширение валидации в модулях встроенного языка #

Идентификатор: com._1c.g5.v8.dt.bsl.externalBslValidator

Описание: Точка расширения, предназначенная для расширения валидации в модулях встроенного языка. Валидировать можно любой объект из семантической модели встроенного языка. Вам нужно реализовать интерфейс com._1c.g5.v8.dt.bsl.validation.IExternalBslValidator

Пример реализации

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * This implementation of {@link IExternalBslValidator} expands validation for Built-In language Form and Command module. <br>
 * 
 * Checked that there is no exports methods in this type of module.
 */
public class FormAndCommandExportMethodValidator implements IExternalBslValidator
{
    public static final String ERROR_CODE = "Export method in form or command module";

    @Override
    public boolean needValidation(EObject object)
    {
        return object instanceof Module;
    }

    @Override
    @Check(CheckType.EXPENSIVE)
    public void validate(EObject object, CustomValidationMessageAcceptor messageAcceptor, CancelIndicator monitor)
    {
		// Проверяем, что у модуля правильный тип
        if (module.getModuleType() == ModuleType.FORM_MODULE || module.getModuleType() == ModuleType.COMMAND_MODULE)
        {
			// Обходим все методы и проверяем экспортируемые они или нет
            for (Method method : module.allMethods())
            {
				// Нашли экспортируемый метод
                if (method.isExport())
                {
					// Создаем диагностику об ошибке
                    messageAcceptor.warning(
                        IMessages.INSTANCE.not_allowed_export_method_in_form_and_command_module(),
                        method, McorePackage.Literals.NAMED_ELEMENT__NAME,
                        ERROR_CODE);
                }
            }
        }
    }
}

Расширение контекста типа, возвращаемого операторами конструкторов во встроенном языке #

Идентификатор: com._1c.g5.v8.dt.bsl.dynamicContextDefProvider

Описание: Точка расширения, предназначенная для расширения контекста типа, возвращаемого конструкторами (операторным и функциональным). Вам нужно реализовать интерфейс com._1c.g5.v8.dt.bsl.typesystem.IDynamicContextDefProvider.

Пример реализации

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Special implementation of {@link IDynamicContextDefProvider} for {@link FunctionStyleCreator}.
 */
public class FunctionCtorDynamicContextDefProvider implements IDynamicContextDefProvider
{
    @Inject
    private IScopeProvider scopeProvider;

    @Override
    public boolean hasDynamicContext(EObject semanticObject, Type computeType, Environments envs)
    {
        return semanticObject instanceof FunctionStyleCreator;
    }
    @Override
    public Type computeDynamicType(EObject semanticObject, Type computeType, Environments envs)
    {
        FunctionStyleCreator creator = (FunctionStyleCreator)semanticObject;
        Expression typeName = creator.getTypeNameExpression();
        if (computeType == null && typeName instanceof StringLiteral
            && ((StringLiteral)typeName).getLines().size() == 1)
        {
            String typeNameStr = ((StringLiteral)typeName).getLines().get(0);
            typeNameStr = typeNameStr.substring(1, typeNameStr.length() - 1);
            if (!typeNameStr.isEmpty())
            {
                IScope scope =  scopeProvider.getScope(semanticObject,
                        BslPackage.Literals.OPERATOR_STYLE_CREATOR__TYPE);
                String[] parts = typeNameStr.split("\\.");
                IEObjectDescription type = scope.getSingleElement(QualifiedName.create(parts));
                if (type != null)
                {
                    return (Type)type.getEObjectOrProxy();
                }
            }
        }
        return computeType;
    }
}

Расширение синтакс-помощника и валидации для строковых литералов #

Идентификатор: com._1c.g5.v8.dt.bsl.stringLiteralProposals

Описание: Точка расширения, предназначенная для расширения синтакс-помощника и валидации для строковых литералов, используемых внутри программных модулей. Вам нужно реализовать интерфейс com._1c.g5.v8.dt.bsl.ui.contentassist.stringliteral.IStringLiteralProposalProvider.

Пример реализации

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
 * Implementation of {@link IStringLiteralProposalProvider} for {@link StringLiteral} using as the first parameter of 
 * global context method "NStr" ("НСтр"). The first parameter of this method contains {@link StringLiteral} which has next template: <br>
 * 
 * <b>"LanguageCode1='some text'; LanguageCode2='some text';...;LanguageCodeN='some text';"</b> where "LanguageCode" is the language code of metadata object {@link Language}. <br>
 * 
 * Each proposal created by this implementation of {@link IStringLiteralProposalProvider} contains template for one language code.
 */
public class StringLiteralProposalNStrMethod implements IStringLiteralProposalProvider
{
    private static final String NSTR_METHOD_NAME = "nstr";
    private static final String NSTR_METHOD_NAME_RU = "нстр";
 
    @Override
    public boolean isAppropriate(StringLiteral context)
    {
		// Рассматриваем только строковые литералы, имеющие одну строку
        if (context.getLines().size() == 1)
        {
			// Находим семантический элемент, соответствующий вызову метода
            Invocation invocation = EcoreUtil2.getContainerOfType(context, Invocation.class);
            if (invocation!= null
                && invocation.getMethodAccess() instanceof StaticFeatureAccess // к методу не должно идти обращение через точку
                && invocation.getParams().indexOf(context) == 0) // строковый литерал должен быть первым параметром у вызываемого метода
            {
                StaticFeatureAccess staticFeatureAccess = (StaticFeatureAccess)invocation.getMethodAccess();
                String methodName = staticFeatureAccess.getName().toLowerCase();
 
				// Имя метода должно быть "NStr" or "НСтр"
                if (NSTR_METHOD_NAME_RU.equals(methodName) || NSTR_METHOD_NAME.equals(methodName))
                {
                    if (!staticFeatureAccess.getFeatureEntries().isEmpty()
                        && staticFeatureAccess.getFeatureEntries().get(0).getFeature() instanceof Method)
                        return true;
                }
            }
        }
        return false;
    }
    @Override
    public List<String> validate(StringLiteral context, IScopeProvider scopeProvider)
    {
        List<String> unknownCodes = Lists.newArrayList();
        List<String> availableCodes = getLanguageCodesFromConfiguration(context, scopeProvider);
        for (String code : getAllLanguageCode(getString(context)))
        {
            if (!availableCodes.contains(code))
                unknownCodes.add(code);
        }
 
		// Нашли неизвестный код языка - возвращаем текст ошибки валидации
        if (!unknownCodes.isEmpty())
        {
            return Lists.newArrayList(MessageFormat.format(
                Messages.StringLiteralProposalNStrMethod_Unknown_language_code,
                Joiner.on(',').join(unknownCodes)));
        }
        else
        {
			// Нет ни одного неизвестного языкового кода
            return Collections.emptyList();
        }
    }
    @Override
    public List<Triple<String, String, IBslStringLiteralProposalImageProvider>> computeProposals(
        StringLiteral context, IScopeProvider scopeProvider, boolean isRussian)
    {
        List<Triple<String, String, IBslStringLiteralProposalImageProvider>> proposals =
            Lists.newArrayList();
        List<String> availableCodes = getAllLanguageCode(getString(context));
        for (String code : getLanguageCodesFromConfiguration(context, scopeProvider))
        {
			 // Нашли код языка, который еще не добавлен в строковый литерал, создаем подсказку для него
            if (!availableCodes.contains(code))
                proposals.add(createProposalByLanguageCode(code));
        }
        return proposals;
    }
    /*
     * Создание подсказки для кода языка.
     */
    private Triple<String, String, IBslStringLiteralProposalImageProvider> createProposalByLanguageCode(String code)
    {
        return Tuples.create("\"" + code + " = '';", code,
            IBslStringLiteralProposalImageProvider.NULL_IMAGE_PROVIDER);
    }
 
    /*
     * Находим все имеющиеся коды языка в конфигурации, используя IScopeProvider.
     */
    private List<String> getLanguageCodesFromConfiguration(StringLiteral literal, IScopeProvider scopeProvider)
    {
        List<String> allCodes = Lists.newArrayList();
 
		// Получаем все объекты конфигурации типа Язык
        IScope scope = scopeProvider.getScope(literal, MdClassPackage.Literals.CONFIGURATION__LANGUAGES);
        for (IEObjectDescription element : scope.getAllElements())
        {
            Language language = (Language)element.getEObjectOrProxy();
            if (language.eIsProxy())
                language = (Language)EcoreUtil.resolve(language, literal);
            if (!Strings.isNullOrEmpty(language.getLanguageCode()))
                allCodes.add(language.getLanguageCode().toLowerCase());
        }
        return allCodes;
    }
 
    private String getString(StringLiteral literal)
    {
        String string = literal.getLines().get(0);
        string = string.substring(1, string.length() - 1);
        return string;
    }
 
    /*
     * Разбираем контент строкового литерала и выделяем оттуда все коды языка.
     */
    private List<String> getAllLanguageCode(String string)
    {
        List<String> languageCodes = Lists.newArrayList();
        String[] parts = string.split(";");
        for (String part : parts)
        {
            int index = part.indexOf('=');
            if (index != -1)
            {
                languageCodes.add(part.substring(0, index).trim().toLowerCase());
            }
        }
        return languageCodes;
    }
}

Расширение набора quick-fix, доступных в программных модулях #

Идентификатор: com._1c.g5.v8.dt.bsl.ui.externalQuickfixProvider

Описание: Точка расширения, предназначенная для создания quick-fix в программных модулях. Вам нужно реализовать абстрактный класс com._1c.g5.v8.dt.bsl.ui.quickfix.AbstractExternalQuickfixProvider

Пример реализации

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
 * Implementation of {@link AbstractExternalQuickfixProvider} for validation issue describing in {link FormAndCommandExportMethodValidator}. <br>
 * 
 * This implementation contains 2 quickfixes:
 * <ul>
 * <li>deleting export keyword</li>
 * <li>deleting export method</li>
 * </ul> 
 */
public class FormAndCommandExportMethodQuickfix extends AbstractExternalQuickfixProvider
{
    // Cвязываем конкретный код ошибки с создаваемым quick-fix
    @Fix(FormAndCommandExportMethodValidator.ERROR_CODE)
    public void processValidationExportMethodInCommandAndFormModule(final Issue issue, IssueResolutionAcceptor acceptor)
    {
		// Добавляем новый quick-fix
        acceptor.accept(
            issue,
            Messages.FormAndCommandExportMethodQuickfix_Delete_export_keyword,
            Messages.FormAndCommandExportMethodQuickfix_Delete_export_keyword,
            null, new ExternalQuickfixModification<Method>(issue, Method.class,
                new Function<Method, TextEdit>() // специальный метод, который определяет область, содержащую ключевое слово 'Экспорт', и удаляет ее  
                {
                    @Override
                    public TextEdit apply(Method method) // метод, на котором была обнаружена ошибка
                    {
						// Дополнительная проверка на экспортируемость
                        if (method.isExport())
                        {
                            List<INode> nodes = NodeModelUtils.findNodesForFeature(method, BslPackage.Literals.METHOD__EXPORT);
                            if (nodes != null && !nodes.isEmpty())
                            {
                                INode exportKeywordNode = nodes.get(0);
                                return new ReplaceEdit(
                                    exportKeywordNode.getTotalOffset(),
                                    exportKeywordNode.getTotalEndOffset() - exportKeywordNode.getTotalOffset(),
                                    StringUtils.EMPTY);
                            }
                        }
                        return null;
                    }
                })); // создаем IModification для quick-fix, который удаляет ключевое слово 'Экспорт'
        // Добавляем еще один quick-fix
        acceptor.accept(issue,
            Messages.FormAndCommandExportMethodQuickfix_Delete_method,
            Messages.FormAndCommandExportMethodQuickfix_Delete_method, null,
            new ExternalQuickfixModification<Method>(issue, Method.class,
                new Function<Method, TextEdit>() // специальный метод, который определяет область, содержащую экспортируемый метод, и удаляет ее  
                {
                    @Override
                    public TextEdit apply(Method method)
                    {
						// Дополнительная проверка на экспортируемость
                        if (method.isExport())
                        {
                            INode node = NodeModelUtils.findActualNodeFor(method);
                            if (node != null)
                            {
                                return new ReplaceEdit(node.getTotalOffset(),
                                    node.getTotalEndOffset() - node.getTotalOffset(),
                                    StringUtils.EMPTY);
                            }
                        }
                        return null;
                    }
                })); // создаем IModification для quick-fix, который удаляет экспортируемый метод
    }
}

Примеры расширения контекстного меню #

Расширение контекстного меню редактора встроенного языка #

Для рассширения контекстного меню редактора встроенного языка достаточно создать точку расширения org.eclipse.ui.menus.

Расширение контекстного меню редактора встроенного языка

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	<extension point="org.eclipse.ui.menus">
		<menuContribution
			locationURI="popup:#TextEditorContext?after=group.open">
			<command commandId="custom_command_id"
				style="push"
				tooltip="command_tooltip">
				<visibleWhen checkEnabled="false">
					<reference definitionId="com._1c.g5.v8.dt.bsl.Bsl.Editor.opened"/>
				</visibleWhen>
			</command>
		</menuContribution>
	</extension>

Расширение контекстного меню панели «Схема» модуля #

Для рассширения контекстного меню панели «Схема» модуля достаточно создать точку расширения org.eclipse.ui.menus.

Расширение контекстного меню панели «Схема» модуля

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<extension
        point="org.eclipse.ui.menus">
    <menuContribution
           locationURI="popup:com._1c.g5.v8.dt.bsl.ui.outline.BslOutlineContextMenuContribution?after=additions">
        <command
              commandId="custom_command_id"
              label="your_command_name"
              style="push"
              tooltip="command_tooltip">
        </command>
    </menuContribution>
</extension>

Пример создания обработчика команды из контекстного меню #

Данный пример содержит в себе шаблон обработчика команды контекстного меню редактора встроенного языка:

Шаблон обработчика

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public abstract class FooHandler
    extends org.eclipse.core.commands.AbstractHandler
{
    @Override
    public final Object execute(ExecutionEvent event) throws ExecutionException
    {
        IWorkbenchPart part = org.eclipse.ui.handlers.HandlerUtil.getActivePart(event);
 
		// Получаем активный XtextEditor
        XtextEditor target = com._1c.g5.v8.dt.bsl.ui.menu.BslHandlerUtil.extractXtextEditor(part);
        if (target != null)
        {
			// Получаем документ, соответствующий активному XtextEditor
            IXtextDocument document = target.getDocument();
			// XtextDocument следует читать и модифицировать, только используя специальные методы "readOnly(...)" и "modify(...)"
            FooTypeValue fooValue = document.readOnly(new IUnitOfWork<FooTypeValue, XtextResource>() 
                {
                    @Override
                    public FooTypeValue exec(XtextResource resource) throws Exception
                    {
                        if (resource.getContents() != null && !resource.getContents().isEmpty())
                        {
							// Получаем корневой элемент семантической модели
                            EObject object = resource.getContents().get(0);
 
							// Проверяем, что данный элемент это модуль встроенного языка
                            if (object instanceof Module)
                            {
                                // Каким-то образом получаем fooValue из модуля
								return getFooValue((Module) object);
                            }
                        }
                        return null;
                    }
                });
			// Обработаем полученное fooValue
			processValue(fooValue);
        }
        return null;
    }
 
	...
}