Skip to content

Commit

Permalink
ФП для табличных частей
Browse files Browse the repository at this point in the history
- предрасчет таблиц
- исправил работу с объединениями
- перевод на новый парсер запросов
- ускорение
- замечания Сонара
- рефакторинг
closes 1c-syntax#1800
  • Loading branch information
artbear committed Jun 10, 2022
1 parent c2fafa2 commit 01edbe2
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@
import com.github._1c_syntax.utils.CaseInsensitivePattern;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@DiagnosticMetadata(
type = DiagnosticType.CODE_SMELL,
Expand All @@ -51,53 +52,79 @@
)
public class RefOveruseDiagnostic extends AbstractSDBLVisitorDiagnostic {

private static final String REF_REGEX = "Ссылка|Reference";
private static final Pattern REF_PATTERN = CaseInsensitivePattern.compile(REF_REGEX);
private static final Pattern REF_PATTERN = CaseInsensitivePattern.compile("Ссылка|Reference");
private static final int BAD_CHILD_COUNT = 3;
private Collection<ParseTree> dataSourceCollection = new ArrayList<>();
public static final int COUNT_OF_TABLE_DOT_REF_DOT_REF = 5;
private Map<String, Boolean> dataSourcesWithTabularFlag = Collections.emptyMap();

@Override
public ParseTree visitQuery(SDBLParser.QueryContext ctx) {
checkQuery(ctx).forEach(diagnosticStorage::addDiagnostic);
return ctx;
}

private Stream<BSLParserRuleContext> checkQuery(SDBLParser.QueryContext ctx) {
var columnsCollection = Trees.findAllRuleNodes(ctx, SDBLParser.RULE_column);

if (columnsCollection.isEmpty()
|| dataSourceCollection.stream().anyMatch(Trees::treeContainsErrors)) {
return ctx;
if (columnsCollection.isEmpty()) {
return Stream.empty();
}

if (dataSourceCollection.isEmpty()) {
performSimpleCheck(columnsCollection);
return ctx;
dataSourcesWithTabularFlag = dataSourcesWithTabularSection(ctx);
if (dataSourcesWithTabularFlag.isEmpty()) {
return getSimpleOverused(columnsCollection);
}

var tableNames = dataSourceCollection.stream()
.map(RefOveruseDiagnostic::getTableNameOrAlias)
.collect(Collectors.toSet());
return getOverused(columnsCollection);
}

columnsCollection.forEach(column -> checkColumnNode((SDBLParser.ColumnContext) column, tableNames));
return ctx;
private static Map<String, Boolean> dataSourcesWithTabularSection(SDBLParser.QueryContext ctx) {
return Trees.findAllRuleNodes(ctx, SDBLParser.RULE_dataSource).stream()
.map(SDBLParser.DataSourceContext.class::cast)
.collect(Collectors.toMap(RefOveruseDiagnostic::getTableNameOrAlias,
RefOveruseDiagnostic::isTableWithTabularSection));
}

private static String getTableNameOrAlias(SDBLParser.DataSourceContext dataSource) {
final var value = Optional.of(dataSource);
return value
.map(SDBLParser.DataSourceContext::alias)
.map(alias -> (ParseTree)alias.name)
.or(() -> value
.map(SDBLParser.DataSourceContext::table)
.map(tableContext -> (ParseTree)tableContext.tableName))
.or(() -> value
.map(SDBLParser.DataSourceContext::parameterTable)
.map(tableContext -> (ParseTree)tableContext.parameter()))
.map(ParseTree::getText)
.orElse("");
}

@Override
public ParseTree visitSelectQuery(SDBLParser.SelectQueryContext ctx) {
this.dataSourceCollection = Trees.findAllRuleNodes(ctx, SDBLParser.RULE_dataSource);
return super.visitSelectQuery(ctx);
private static boolean isTableWithTabularSection(SDBLParser.DataSourceContext dataSourceContext) {
final var table = dataSourceContext.table();
if (table == null) {
return dataSourceContext.virtualTable() != null;
}
return table.tableName != null || table.objectTableName != null;
}

private void performSimpleCheck(Collection<ParseTree> columnsCollection) {
columnsCollection.stream()
private static Stream<BSLParserRuleContext> getSimpleOverused(Collection<ParseTree> columnsCollection) {
return columnsCollection.stream()
.filter(columnNode -> columnNode.getChildCount() > BAD_CHILD_COUNT)
.map(column -> column.getChild(column.getChildCount() - 1))
.filter(lastChild -> REF_PATTERN.matcher(lastChild.getText()).matches())
.forEach(node -> diagnosticStorage.addDiagnostic((BSLParserRuleContext) node));
.map(BSLParserRuleContext.class::cast);
}

private void checkColumnNode(SDBLParser.ColumnContext ctx, Set<String> tableNames) {
private Stream<BSLParserRuleContext> getOverused(Collection<ParseTree> columnsCollection) {
return columnsCollection.stream()
.map(SDBLParser.ColumnContext.class::cast)
.filter(column -> column.getChildCount() >= BAD_CHILD_COUNT)
.filter(this::isOveruse)
.map(BSLParserRuleContext.class::cast);
}

if (ctx.children == null) {
return;
}
private boolean isOveruse(SDBLParser.ColumnContext ctx) {

// children:
//
Expand All @@ -111,39 +138,24 @@ private void checkColumnNode(SDBLParser.ColumnContext ctx, Set<String> tableName

final int childCount = ctx.children.size();

if (childCount < 3) {
return;
}

var lastChild = ctx.getChild(childCount - 1);
// dots are also children of ColumnContext,
// that is why -3 must be an index of penultimate identifier
var penultimateChild = ctx.getChild(childCount - 3);
var penultimateChild = ctx.getChild(childCount - BAD_CHILD_COUNT);

String lastIdentifierName = lastChild.getText();
String penultimateIdentifierName = penultimateChild.getText();

if (REF_PATTERN.matcher(penultimateIdentifierName).matches()
|| (REF_PATTERN.matcher(lastIdentifierName).matches()
&& !tableNames.contains(penultimateIdentifierName))) {
diagnosticStorage.addDiagnostic(ctx);
if (REF_PATTERN.matcher(penultimateIdentifierName).matches()) {
if (childCount < COUNT_OF_TABLE_DOT_REF_DOT_REF){
return true;
}
var prevChildID = ctx.getChild(childCount - COUNT_OF_TABLE_DOT_REF_DOT_REF).getText();
return !dataSourcesWithTabularFlag.getOrDefault(prevChildID, false);
}
}

private static String getTableNameOrAlias(ParseTree dataSource) {
return Optional.of(dataSource)
.flatMap(dataSrc -> extractTextFromChild(dataSrc, SDBLParser.RULE_alias))
.or(() -> Optional.of(dataSource)
.flatMap(dataSrc -> extractTextFromChild(dataSrc, SDBLParser.RULE_table)))
.or(() -> Optional.of(dataSource)
.flatMap(dataSrc -> extractTextFromChild(dataSrc, SDBLParser.RULE_parameterTable)))
.orElse("");
}

private static Optional<String> extractTextFromChild(ParseTree parseTree, int childRuleType) {
return Optional.of(parseTree)
.flatMap(tree -> Trees.getFirstChild(tree, childRuleType))
.flatMap(child -> Trees.getFirstChild(child, SDBLParser.RULE_identifier))
.map(BSLParserRuleContext::getText);
var lastChild = ctx.getChild(childCount - 1);
String lastIdentifierName = lastChild.getText();
if (REF_PATTERN.matcher(lastIdentifierName).matches()) {
return dataSourcesWithTabularFlag.get(penultimateIdentifierName) == null;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ void test() {

List<Diagnostic> diagnostics = getDiagnostics();

assertThat(diagnostics).hasSize(12);
assertThat(diagnostics, true)
.hasRange(3, 28, 3, 45)
.hasRange(13, 8, 13, 34)
Expand All @@ -51,7 +50,7 @@ void test() {
.hasRange(92, 8, 29)
.hasRange(153, 13, 153, 41)
.hasRange(164, 13, 164, 53)
.hasRange(178, 13, 178, 35);

.hasRange(178, 13, 178, 35)
.hasSize(12);
}
}
22 changes: 21 additions & 1 deletion src/test/resources/diagnostics/RefOveruseDiagnostic.bsl
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,30 @@
| Контрагенты.Ссылка.ИНН = &ИНН"; // ошибка
КонецПроцедуры

Процедура Тест17()
Процедура Тест16()
ТекстЗапроса =
"ВЫБРАТЬ
| Артикул КАК Артикул // не задано имя таблицы
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
КонецПроцедуры

Процедура Тест17()
ТекстЗапроса =
"ВЫБРАТЬ
| Таблица.Ссылка КАК Ссылка
|ИЗ
| Документ.Документ1.ТабличнаяЧасть1 КАК Таблица
|ГДЕ
| Таблица.Ссылка.Реквизит1 = &ИНН"; // не ошибка
КонецПроцедуры

Процедура Тест18()
ТекстЗапроса =
"ВЫБРАТЬ
| Таблица.Ссылка КАК Ссылка
|ИЗ
| РегистрСведений.РегистрСведений1.СрезПоследних КАК Таблица
|ГДЕ
| Таблица.Ссылка.Реквизит1 = &ИНН"; // не ошибка
КонецПроцедуры

0 comments on commit 01edbe2

Please sign in to comment.