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

com.github.sbaudoin.sonar.plugins.shellcheck.highlighting.ShellHighlighting Maven / Gradle / Ivy

/**
 * Copyright (c) 2018-2019, Sylvain Baudoin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.sbaudoin.sonar.plugins.shellcheck.highlighting;

import com.github.sbaudoin.sonar.plugins.shellcheck.lexer.BashLexer;
import com.github.sbaudoin.sonar.plugins.shellcheck.lexer.Token;
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Class in charge of Shell code highlighting in SonarQube
 */
public class ShellHighlighting {
    private static final Logger LOGGER = Loggers.get(ShellHighlighting.class);

    private List highlighting = new ArrayList<>();
    private String code;


    /**
     * Constructor
     *
     * @param script the Shell code to be highlighted
     * @throws IllegalArgumentException if {@code script} is {@code null}
     */
    public ShellHighlighting(String script) {
        if (script == null) {
            throw new IllegalArgumentException("Input source code cannot be null");
        }
        this.code = script;
        try {
            new BashLexer(new StringReader(script)).scan().forEach(this::highlightToken);
        } catch (IOException e) {
            LOGGER.warn("Could not scan Shell script and highlight code", e);
        }
    }


    /**
     * Returns the list of highlighting data found for the Shell script
     *
     * @return the list of highlighting data found for the Shell script (possibly empty but never {@code null})
     */
    public List getHighlightingData() {
        return highlighting;
    }


    /**
     * Creates an {@code HighlightingData} for a code token
     *
     * @param token a token to be highlighted
     */
    private void highlightToken(Token token) {
        switch (token.type) {
            case COMMENT: case SHEBANG:
                LOGGER.trace(String.format("Highlighting comment at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.COMMENT);
                break;

            case ARITH_ASS_BIT_AND: case ARITH_ASS_BIT_OR: case ARITH_ASS_BIT_XOR: case ARITH_ASS_DIV:
            case ARITH_ASS_MINUS: case ARITH_ASS_MOD: case ARITH_ASS_MUL: case ARITH_ASS_PLUS:
            case ARITH_ASS_SHIFT_LEFT: case ARITH_ASS_SHIFT_RIGHT: case ARITH_BASE_CHAR: case ARITH_BITWISE_AND:
            case ARITH_BITWISE_NEGATE: case ARITH_BITWISE_XOR: case ARITH_COLON: case ARITH_DIV:
            case ARITH_EQ: case ARITH_EXPONENT: case ARITH_GE: case ARITH_GT: case ARITH_LE:
            case ARITH_LT: case ARITH_MINUS: case ARITH_MINUS_MINUS: case ARITH_MOD: case ARITH_MULT:
            case ARITH_NE: case ARITH_NEGATE: case ARITH_PLUS: case ARITH_PLUS_PLUS: case ARITH_QMARK:
            case ARITH_SHIFT_LEFT: case ARITH_SHIFT_RIGHT: case AND_AND: case OR_OR: case EQ:
            case COND_OP: case COND_OP_EQ_EQ: case COND_OP_NOT: case COND_OP_REGEX:
                LOGGER.trace(String.format("Highlighting arithmetic expression at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.KEYWORD_LIGHT);
                break;

            case CASE_KEYWORD: case BRACKET_KEYWORD: case DO_KEYWORD: case DONE_KEYWORD:
            case ELIF_KEYWORD: case ELSE_KEYWORD: case ESAC_KEYWORD: case FI_KEYWORD: case FOR_KEYWORD:
            case FUNCTION_KEYWORD: case IF_KEYWORD: case LET_KEYWORD: case SELECT_KEYWORD:
            case THEN_KEYWORD: case TIME_KEYWORD: case TRAP_KEYWORD: case UNTIL_KEYWORD: case WHILE_KEYWORD:
            case ASSIGNMENT_WORD:
                LOGGER.trace(String.format("Highlighting keyword at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.KEYWORD);
                break;

            case HEREDOC_LINE: case HEREDOC_CONTENT: case HEREDOC_MARKER_END: case HEREDOC_MARKER_IGNORING_TABS_END:
            case HEREDOC_MARKER_START: case HEREDOC_MARKER_TAG:
                LOGGER.trace(String.format("Highlighting label at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.STRUCTURED_COMMENT);
                break;

            case VARIABLE:
                LOGGER.trace(String.format("Highlighting identifier at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.CONSTANT);
                break;

            case ARITH_HEX_NUMBER: case ARITH_NUMBER: case ARITH_OCTAL_NUMBER: case INTEGER_LITERAL:
                LOGGER.trace(String.format("Highlighting number at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.CONSTANT);
                break;

            case STRING_BEGIN: case STRING_END: case STRING_DATA: case STRING_CONTENT: case STRING2: case BACKQUOTE:
                LOGGER.trace(String.format("Highlighting string or regex at %d:%d", token.line, token.column));
                addHighlighting(token, TypeOfText.STRING);
                break;

            default:
                break;
        }
    }

    /**
     * Creates an {@code HighlightingData} with the passed characteristics
     *
     * @param token the token to highlighting
     * @param typeOfText the type of highlighted text
     */
    private void addHighlighting(Token token, TypeOfText typeOfText) {
        if (token.length == 0) {
            throw new IllegalArgumentException("Cannot highlight an empty token");
        }

        ShellLocation start = new ShellLocation(code, token);
        ShellLocation end = start.shift(token.length);
        highlighting.add(new HighlightingData(start.line(), start.column(), end.line(), end.column(), typeOfText));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy