All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.iac.docker.checks.utils.ArgumentResolutionSplitter Maven / Gradle / Ivy

/*
 * SonarQube IaC Plugin
 * Copyright (C) 2021-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.iac.docker.checks.utils;

import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sonar.iac.docker.checks.utils.command.SeparatedList;
import org.sonar.iac.docker.symbols.ArgumentResolution;
import org.sonar.iac.docker.tree.api.Argument;
import org.sonar.iac.docker.tree.api.SyntaxToken;
import org.sonar.iac.docker.tree.impl.ArgumentImpl;
import org.sonar.iac.docker.tree.impl.LiteralImpl;
import org.sonar.iac.docker.tree.impl.SyntaxTokenImpl;

import static org.sonar.iac.common.api.tree.impl.TextRanges.range;

public final class ArgumentResolutionSplitter {

  private static final String STRING_IN_DOUBLE_QUOTES = "\"(?:\\\\.|[^\"])*+\"";
  private static final String STRING_IN_SIMPLE_QUOTES = "'(?:\\\\.|[^'])*+'";
  private static final String NON_SEPARATOR_CHARACTER = "[^;&|]";
  private static final String COMMAND_WITHOUT_OPERATOR = "(?:" + STRING_IN_DOUBLE_QUOTES + "|" + STRING_IN_SIMPLE_QUOTES + "|" + NON_SEPARATOR_CHARACTER + ")*+";
  private static final String OPERATORS = "(?:;|&&|&|\\|\\||\\|)";

  private static final String FIRST_COMMAND = "firstCommand";
  private static final String REMAINDER = "remainder";
  /**
   * Validate that the provided input is a mix of commands separated by operators. Split them as firstCommand and the rest.
   */
  private static final Pattern FIRST_COMMAND_AND_REST_REGEX = Pattern.compile(
    "^(?<" + FIRST_COMMAND + ">" + COMMAND_WITHOUT_OPERATOR + ")(?<" + REMAINDER + ">(?:" + OPERATORS + COMMAND_WITHOUT_OPERATOR + ")++)$");

  private static final String OPERATOR = "operator";
  private static final String COMMAND = "command";
  /**
   * Parse repeating operators and commands in the rest part of the {@link #FIRST_COMMAND_AND_REST_REGEX}.
   */
  private static final Pattern OPERATOR_AND_COMMAND_REGEX = Pattern.compile(
    "(?<" + OPERATOR + ">" + OPERATORS + ")(?<" + COMMAND + ">" + COMMAND_WITHOUT_OPERATOR + ")");

  private ArgumentResolutionSplitter() {
  }

  /**
   * Split commands by separators: {@code &&}, {@code ||}, {@code &}, {@code |} and {@code ;}.
   */
  public static SeparatedList, String> splitCommands(Iterable resolvedArguments) {
    var separatedListBuilder = new CommandDetector.SeparatedListBuilder();

    for (ArgumentResolution resolvedArgument : resolvedArguments) {
      parseCommand(separatedListBuilder, resolvedArgument);
    }
    return separatedListBuilder.build();
  }

  private static void parseCommand(CommandDetector.SeparatedListBuilder separatedListBuilder, ArgumentResolution resolvedArgument) {
    String argument = resolvedArgument.value();
    var fullMatcher = FIRST_COMMAND_AND_REST_REGEX.matcher(argument);
    if (fullMatcher.find()) {
      String firstCommand = fullMatcher.group(FIRST_COMMAND);
      String remainder = fullMatcher.group(REMAINDER);
      if (!firstCommand.isBlank()) {
        ArgumentResolution newResolvedArg = buildSubArgument(resolvedArgument, firstCommand, 0);
        separatedListBuilder.addToCurrentCommand(newResolvedArg);
      }
      parseRemainderOfTheCommand(separatedListBuilder, resolvedArgument, fullMatcher, remainder);
    } else {
      separatedListBuilder.addToCurrentCommand(resolvedArgument);
    }
  }

  private static void parseRemainderOfTheCommand(
    CommandDetector.SeparatedListBuilder separatedListBuilder,
    ArgumentResolution resolvedArgument,
    Matcher fullMatcher,
    String remainder) {
    var matcher = OPERATOR_AND_COMMAND_REGEX.matcher(remainder);
    while (matcher.find()) {
      String operator = matcher.group(OPERATOR);
      String command = matcher.group(COMMAND);
      separatedListBuilder.addOperator(operator);
      if (!command.isBlank()) {
        ArgumentResolution newResolvedArg = buildSubArgument(resolvedArgument, command, fullMatcher.start(REMAINDER) + matcher.start(COMMAND));
        separatedListBuilder.addToCurrentCommand(newResolvedArg);
      }
    }
  }

  private static ArgumentResolution buildSubArgument(ArgumentResolution resolvedArgument, String firstCommand, int offsetShift) {
    var argumentRange = resolvedArgument.argument().textRange();
    SyntaxToken token = new SyntaxTokenImpl(firstCommand, range(argumentRange.start().line(), argumentRange.start().lineOffset() + offsetShift, firstCommand),
      Collections.emptyList());
    var literal = new LiteralImpl(token);
    token.setParent(literal);
    Argument newArg = new ArgumentImpl(List.of(literal));
    literal.setParent(newArg);
    // workaround to keep the quotes preservation logic of ArgumentResolution who checks if the parent is ShellForm
    newArg.setParent(resolvedArgument.argument().parent());
    return ArgumentResolution.ofWithoutStrippingQuotes(newArg);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy